<?php
if (!defined('ABSPATH')) {
    exit;
}


// Function to display the typing text section (global version)
if (!function_exists('aysa_display_typing_text_section_global')) {
    function aysa_display_typing_text_section_global($text_to_type, $element_id_suffix) {
        ob_start();
        ?>
        <div class="aysa-chat-container aysa-wizard-intro-text aysa-global-intro-text">
            <div class="aysa-chat-message aysa-ai-message">
                <p id="aysa-dynamic-text-<?php echo esc_attr($element_id_suffix); ?>"></p>
            </div>
        </div>
        <script type="text/javascript">
            document.addEventListener('DOMContentLoaded', function() {
                const uniqueSuffixGlobal = '<?php echo esc_js($element_id_suffix); ?>';
                const targetElementIdGlobal = 'aysa-dynamic-text-' + uniqueSuffixGlobal;
                // console.log('[Aysa Typing Effect DEBUG Global] Trying to initialize for suffix: ' + uniqueSuffixGlobal + ', Target ID: ' + targetElementIdGlobal);

                const textElementGlobal = document.getElementById(targetElementIdGlobal);
                const textToTypeGlobal = <?php echo json_encode($text_to_type); ?>;
                let currentIndexGlobal = 0;
                const typingSpeedGlobal = 30; // Milliseconds per character

                // console.log('[Aysa Typing Effect DEBUG Global ' + uniqueSuffixGlobal + '] Target Element:', textElementGlobal);
                // console.log('[Aysa Typing Effect DEBUG Global ' + uniqueSuffixGlobal + '] Text to type:', textToTypeGlobal);

                function typeWriterGlobal() {
                    if (textElementGlobal && currentIndexGlobal < textToTypeGlobal.length) {
                        textElementGlobal.innerHTML += textToTypeGlobal.charAt(currentIndexGlobal);
                        currentIndexGlobal++;
                        setTimeout(typeWriterGlobal, typingSpeedGlobal);
                    } else if (textElementGlobal) {
                        textElementGlobal.classList.add('typing-done');
                        // console.log('[Aysa Typing Effect DEBUG Global ' + uniqueSuffixGlobal + '] Typing complete.');
                    } else {
                        // console.error('[Aysa Typing Effect DEBUG Global ' + uniqueSuffixGlobal + '] Target element became null during typing.');
                    }
                }

                if (textElementGlobal) {
                    textElementGlobal.innerHTML = ''; // Clear initial content
                    // console.log('[Aysa Typing Effect DEBUG Global ' + uniqueSuffixGlobal + '] Starting typewriter.');
                    typeWriterGlobal();
                } else {
                    // console.error('[Aysa Typing Effect DEBUG Global ' + uniqueSuffixGlobal + '] Target element NOT FOUND at initialization for ID: ' + targetElementIdGlobal);
                }
            });
        </script>
        <?php
        return ob_get_clean();
    }
}

class Aysa_Admin {
    private $plugin_name;
    private $version;
    public $credits_component;
    private $setup_gate_notice = array();

    public function __construct($plugin_name, $version) {
        $this->plugin_name = $plugin_name;
        $this->version = $version;
        
        // Initialize credits component with demo user ID (0)
        $this->credits_component = new Aysa_Credits_Component(function_exists('get_current_user_id') ? get_current_user_id() : 0);
        add_action('init', array($this, 'schedule_gbp_social_calendar_cron'));
        add_action('aysa_gbp_social_calendar_publish_event', array($this, 'handle_gbp_social_calendar_publish_event'));
        
        // Register wizard data handler
        add_action('wp_ajax_aysa_save_wizard_data', array($this, 'handle_save_wizard_data'));
        add_action('wp_ajax_aysa_setup_save_tone_of_voice', array($this, 'handle_setup_save_tone_of_voice'));
        add_action('wp_ajax_aysa_setup_api_proxy', array($this, 'handle_setup_api_proxy'));
        add_action('wp_ajax_aysa_setup_general_autofill', array($this, 'handle_setup_general_autofill'));
        add_action('wp_ajax_aysa_setup_conversation', array($this, 'handle_setup_conversation'));
        add_action('wp_ajax_aysa_adjustments_conversation', array($this, 'handle_adjustments_conversation'));
        add_action('wp_ajax_aysa_project_jobs_status', array($this, 'handle_project_jobs_status'));
        add_action('wp_ajax_aysa_conversation_async_start', array($this, 'handle_conversation_async_start'));
        add_action('wp_ajax_aysa_conversation_async_status', array($this, 'handle_conversation_async_status'));
        add_action('wp_ajax_aysa_keyword_research_run_status', array($this, 'handle_keyword_research_run_status'));
        if (class_exists('Aysa_Projection_Sync_Controller')) {
            (new Aysa_Projection_Sync_Controller($this))->register_hooks();
        }
        add_action('wp_ajax_aysa_pagespeed_report_email', array($this, 'handle_pagespeed_report_email'));
        add_action('wp_ajax_aysa_adjustments_generation_notifications', array($this, 'handle_adjustments_generation_notifications'));
        add_action('wp_ajax_aysa_setup_notifications', array($this, 'handle_setup_notifications'));
        add_action('wp_ajax_aysa_workspace_state_save', array($this, 'handle_workspace_state_save'));
        add_action('wp_ajax_aysa_insight_banner_dismiss', array($this, 'handle_insight_banner_dismiss'));
        add_action('aysa_adjustments_generate_created_entity', array($this, 'handle_adjustments_generate_created_entity'), 10, 1);
        add_action('wp_ajax_aysa_setup_reset_chat_test', array($this, 'handle_setup_reset_chat_test'));
        add_action('wp_ajax_aysa_apprentice_reset_test', array($this, 'handle_apprentice_reset_test'));
        add_action('wp_ajax_aysa_agent8_factory_reset_dry_run', array($this, 'handle_agent8_factory_reset_dry_run'));
        add_action('wp_ajax_aysa_agent8_factory_reset_execute', array($this, 'handle_agent8_factory_reset_execute'));
        add_action('wp_ajax_aysa_agent8_factory_reset_execute_fast', array($this, 'handle_agent8_factory_reset_execute_fast'));
        add_action('wp_ajax_aysa_access_key_get_test', array($this, 'handle_access_key_get_test'));
        add_action('wp_ajax_aysa_access_key_verify', array($this, 'handle_access_key_verify'));
        add_action('wp_ajax_aysa_activation_conversation', array($this, 'handle_activation_conversation'));
        add_action('wp_ajax_aysa_activation_reset_test', array($this, 'handle_activation_reset_test'));
        add_action('wp_ajax_aysa_agent8_conversation', array($this, 'handle_agent8_conversation'));
        add_action('wp_ajax_aysa_agent9_conversation', array($this, 'handle_agent9_conversation'));
        add_action('wp_ajax_aysa_agent8v3_image_alt_test_prepare', array($this, 'handle_agent8v3_image_alt_test_prepare'));
        add_action('wp_ajax_aysa_agent8v3_image_alt_test_run', array($this, 'handle_agent8v3_image_alt_test_run'));
        add_action('wp_ajax_aysa_agent8v3_image_alt_test_disable', array($this, 'handle_agent8v3_image_alt_test_disable'));
        add_action('wp_ajax_aysa_agent8v3_image_alt_test_history', array($this, 'handle_agent8v3_image_alt_test_history'));
        add_action('wp_ajax_aysa_agent8v3_image_alt_billing_identity_repair', array($this, 'handle_agent8v3_image_alt_billing_identity_repair'));
        add_action('wp_ajax_aysa_agent8v3_image_alt_product_gates_set', array($this, 'handle_agent8v3_image_alt_product_gates_set'));
        add_action('wp_ajax_aysa_agent8v3_image_optimization_product_gates_set', array($this, 'handle_agent8v3_image_optimization_product_gates_set'));
        add_action('wp_ajax_aysa_agent8v3_canonical_product_gates_set', array($this, 'handle_agent8v3_canonical_product_gates_set'));
        add_action('wp_ajax_aysa_agent8v3_metadata_product_gates_set', array($this, 'handle_agent8v3_metadata_product_gates_set'));
        add_action('wp_ajax_aysa_agent8v3_schema_product_gates_set', array($this, 'handle_agent8v3_schema_product_gates_set'));
        add_action('wp_ajax_aysa_agent8v3_internal_linking_product_gates_set', array($this, 'handle_agent8v3_internal_linking_product_gates_set'));
        add_action('wp_ajax_aysa_agent8v3_internal_linking_rejection_action', array($this, 'handle_agent8v3_internal_linking_rejection_action'));
        add_action('wp_ajax_aysa_agent8v3_onpage_content_rewrite_product_gates_set', array($this, 'handle_agent8v3_onpage_content_rewrite_product_gates_set'));
        add_action('wp_ajax_aysa_agent8v3_onpage_content_rewrite_live_refresh', array($this, 'handle_agent8v3_onpage_content_rewrite_live_refresh'));
        add_action('wp_ajax_aysa_agent8v3_onpage_content_rewrite_history_action', array($this, 'handle_agent8v3_onpage_content_rewrite_history_action'));
        add_action('wp_ajax_aysa_agent8v3_internal_linking_history_action', array($this, 'handle_agent8v3_internal_linking_history_action'));
        add_action('wp_ajax_aysa_agent8v3_schema_controlled_candidates_prepare', array($this, 'handle_agent8v3_schema_controlled_candidates_prepare'));
        add_action('wp_ajax_aysa_agent8v3_schema_controlled_candidates_clear', array($this, 'handle_agent8v3_schema_controlled_candidates_clear'));
        add_action('wp_ajax_aysa_agent8v3_image_alt_test_restore', array($this, 'handle_agent8v3_image_alt_test_restore'));
        add_action('wp_ajax_aysa_agent8v3_redirect_product_gates_set', array($this, 'handle_agent8v3_redirect_product_gates_set'));
        if (class_exists('Aysa_Agent8_V3_Proxy_Controller')) {
            (new Aysa_Agent8_V3_Proxy_Controller($this))->register_hooks();
        }
        add_action('template_redirect', array($this, 'maybe_serve_agent8v3_redirect_runtime_provider'), 1);
        add_action('wp_ajax_aysa_publish_generated_article_to_post', array($this, 'handle_publish_generated_article_to_post'));
        add_action('wp_ajax_aysa_export_pagespeed_pdf', array($this, 'handle_export_pagespeed_pdf'));
        add_action('wp_ajax_aysa_export_pagespeed_pdf_status', array($this, 'handle_export_pagespeed_pdf_status'));
        add_action('admin_init', array($this, 'redirect_deprecated_agent_pages'), 0);
        add_action('admin_init', array($this, 'enforce_access_key_for_admin_pages'), 1);
        add_action('admin_init', array($this, 'enforce_setup_gate_for_admin_pages'), 2);
        add_action('admin_init', array($this, 'maybe_cleanup_retired_agent7_runtime'), 3);
        add_action('admin_notices', array($this, 'render_setup_gate_notice'));
        add_action('admin_notices', array($this, 'render_notifications_notice'));
        add_action('init', array($this, 'enforce_access_key_for_aysa_ajax'), 1);
        add_action('current_screen', array($this, 'maybe_disable_wc_admin_preload_on_aysa_pages'), 1);
        
        // Register WooCommerce categories handler
        add_action('wp_ajax_get_woocommerce_categories', array($this, 'get_woocommerce_categories'));
        
        // Register .htaccess redirects handler
        add_action('wp_ajax_aysa_add_htaccess_redirect', array($this, 'handle_add_htaccess_redirect'));
        add_action('wp_ajax_aysa_get_local_redirects', array($this, 'handle_get_local_redirects'));
        add_action('wp_ajax_aysa_delete_local_redirect', array($this, 'handle_delete_local_redirect'));
        
        // Register .htaccess gone URL handler
        add_action('wp_ajax_aysa_add_htaccess_gone', array($this, 'handle_add_htaccess_gone'));
        
        // Register .htaccess remove redirect handler
        add_action('wp_ajax_aysa_remove_htaccess_redirect', array($this, 'handle_remove_htaccess_redirect'));
        
        // Keep destructive cleanup tooling disabled in live/client runtime unless explicitly enabled.
        if ($this->aysa_allow_destructive_tools()) {
            add_action('wp_ajax_aysa_cleanup_suggested_keywords', array($this, 'handle_cleanup_suggested_keywords'));
            add_action('wp_ajax_aysa_cleanup_get_associations', array($this, 'handle_cleanup_get_associations'));
            add_action('wp_ajax_aysa_cleanup_associations_batch', array($this, 'handle_cleanup_associations_batch'));
            add_action('wp_ajax_aysa_cleanup_wordpress_meta', array($this, 'handle_cleanup_wordpress_meta'));
            add_action('wp_ajax_aysa_cleanup_generated_titles', array($this, 'handle_cleanup_generated_titles'));
            add_action('wp_ajax_aysa_cleanup_content_strategies', array($this, 'handle_cleanup_content_strategies'));
            add_action('wp_ajax_aysa_cleanup_local_project_data', array($this, 'handle_cleanup_local_project_data'));
        }
        
        // Register .htaccess remove gone URL handler
        add_action('wp_ajax_aysa_remove_htaccess_gone', array($this, 'handle_remove_htaccess_gone'));
        
        // Register AJAX handler for redirect suggestions
        add_action('wp_ajax_aysa_get_redirect_suggestions', array($this, 'ajax_get_redirect_suggestions'));
        
        // Register AJAX handler for settings
        add_action('wp_ajax_aysa_save_settings', array($this, 'handle_save_settings'));
        add_action('wp_ajax_aysa_notifications_fetch', array($this, 'handle_notifications_fetch'));
        add_action('wp_ajax_aysa_agent8_notifications_fetch', array($this, 'handle_notifications_fetch'));
        add_action('wp_ajax_aysa_notifications_mark_read', array($this, 'handle_notifications_mark_read'));
        add_action('wp_ajax_aysa_notifications_mark_all_read', array($this, 'handle_notifications_mark_all_read'));
        
        // Register AJAX handlers for automation QA framework
        add_action('wp_ajax_aysa_get_feature_stats', array($this, 'handle_get_feature_stats'));
        add_action('wp_ajax_aysa_check_acceptance_rate', array($this, 'handle_check_acceptance_rate'));
        add_action('wp_ajax_aysa_save_automation_setting', array($this, 'handle_save_automation_setting'));
        add_action('wp_ajax_aysa_save_qa_settings', array($this, 'handle_save_qa_settings'));
        add_action('wp_ajax_aysa_submit_feedback', array($this, 'handle_submit_feedback'));
        
        // Register AJAX handler for social media content generation
        add_action('wp_ajax_aysa_generate_social_content', array($this, 'handle_generate_social_content'));
        
        // Register automation AJAX handlers
        add_action('wp_ajax_aysa_get_scheduled_tasks', array($this, 'handle_get_scheduled_tasks'));
        add_action('wp_ajax_aysa_schedule_domain_authority_task', array($this, 'handle_schedule_domain_authority_task'));
        add_action('wp_ajax_aysa_schedule_association_task', array($this, 'handle_schedule_association_task'));
        add_action('wp_ajax_aysa_add_task_to_queue', array($this, 'handle_add_task_to_queue'));
        add_action('wp_ajax_aysa_add_pending_review', array($this, 'handle_add_pending_review'));
        
        // Add AJAX hook for loading automation content in Adjustment & Improvement tab
        add_action('wp_ajax_aysa_load_automation_content', array($this, 'handle_load_automation_content'));
        
        // Register status update handler
        add_action('wp_ajax_aysa_update_status', array($this, 'handle_update_status'));
        
        if (class_exists('Aysa_Offpage_Admin_Controller')) {
            (new Aysa_Offpage_Admin_Controller($this))->register_hooks();
        }
        if (class_exists('Aysa_Monitoring_Admin_Controller')) {
            (new Aysa_Monitoring_Admin_Controller($this))->register_hooks();
        }
        add_action('wp_ajax_aysa_onpage_get_associations', array($this, 'handle_onpage_get_associations'));
        add_action('wp_ajax_aysa_onpage_association_proxy', array($this, 'handle_onpage_association_proxy'));
        add_action('wp_ajax_aysa_onpage_workspace', array($this, 'handle_onpage_workspace'));
        add_action('wp_ajax_aysa_onpage_detail', array($this, 'handle_onpage_detail'));
        add_action('wp_ajax_aysa_onpage_start_run', array($this, 'handle_onpage_start_run'));
        add_action('wp_ajax_aysa_onpage_run_status', array($this, 'handle_onpage_run_status'));
        add_action('wp_ajax_aysa_onpage_entity_action', array($this, 'handle_onpage_entity_action_v2'));
        add_action('wp_ajax_aysa_onpage_batch_action', array($this, 'handle_onpage_batch_action_v2'));
        add_action('wp_ajax_aysa_get_project_by_domain_broker', array($this, 'handle_get_project_by_domain_broker'));
        add_action('wp_ajax_aysa_research_async_start', array($this, 'handle_research_async_start'));
        add_action('wp_ajax_aysa_research_async_status', array($this, 'handle_research_async_status'));
        add_action('wp_ajax_aysa_sitemap_competition_get_overview', array($this, 'handle_sitemap_competition_get_overview'));
        add_action('wp_ajax_aysa_sitemap_competition_start_scan', array($this, 'handle_sitemap_competition_start_scan'));
        add_action('wp_ajax_aysa_sitemap_competition_get_status', array($this, 'handle_sitemap_competition_get_status'));
        add_action('wp_ajax_aysa_sitemap_competition_get_detail', array($this, 'handle_sitemap_competition_get_detail'));
        add_action('wp_ajax_aysa_sitemap_competition_get_history', array($this, 'handle_sitemap_competition_get_history'));
        add_action('aysa_research_async_event', array($this, 'run_research_async_event'), 10, 1);
        add_action('aysa_conversation_async_event', array($this, 'run_conversation_async_event'), 10, 1);
        add_action('aysa_sitemap_competition_scan_event', array($this, 'run_sitemap_competition_scan_event'), 10, 1);
        add_action('aysa_technical_site_audit_poll_event', array($this, 'handle_technical_site_audit_poll_event'), 10, 1);
        add_filter('cron_schedules', array($this, 'register_aysa_cron_schedules'));
        add_action('admin_init', array($this, 'aysa_sitemap_competition_maybe_create_tables'));
        // Register AJAX action for link recheck
        add_action('wp_ajax_aysa_recheck_link', array($this, 'handle_recheck_link'));
        
        // Register AJAX action for cancel WordToLink
        add_action('wp_ajax_aysa_cancel_wordtolink', array($this, 'handle_cancel_wordtolink'));
        
        // Register AJAX action for cached suggested keywords
        add_action('wp_ajax_aysa_get_suggested_keywords_cached', array($this, 'handle_get_suggested_keywords_cached'));
        add_action('wp_ajax_aysa_keyword_discovery_get_associations', array($this, 'handle_keyword_discovery_get_associations'));
        add_action('wp_ajax_aysa_keyword_discovery_upsert_association', array($this, 'handle_keyword_discovery_upsert_association'));
        add_action('wp_ajax_aysa_keyword_discovery_get_keywords_and_competitors', array($this, 'handle_keyword_discovery_get_keywords_and_competitors'));
        add_action('wp_ajax_aysa_keyword_discovery_local_keywords', array($this, 'handle_keyword_discovery_local_keywords'));
        add_action('wp_ajax_aysa_keyword_discovery_generate_strategy', array($this, 'handle_keyword_discovery_generate_strategy'));
        add_action('wp_ajax_aysa_keyword_discovery_get_strategy', array($this, 'handle_keyword_discovery_get_strategy'));
        add_action('wp_ajax_aysa_keyword_discovery_refresh_start_background', array($this, 'handle_keyword_discovery_refresh_start_background'));
        add_action('wp_ajax_aysa_keyword_discovery_refresh_status', array($this, 'handle_keyword_discovery_refresh_status'));
        add_action('wp_ajax_aysa_keyword_discovery_apply_staged_to_local', array($this, 'handle_keyword_discovery_apply_staged_to_local'));
        add_action('wp_ajax_aysa_keyword_discovery_keyword_suggestions_refresh', array($this, 'handle_keyword_discovery_keyword_suggestions_refresh'));
        add_action('wp_ajax_aysa_keyword_discovery_intent_batch', array($this, 'handle_keyword_discovery_intent_batch'));
        add_action('wp_ajax_aysa_keyword_discovery_v2_gsc_raw', array($this, 'handle_keyword_discovery_v2_gsc_raw'));
        add_action('wp_ajax_aysa_keyword_discovery_v2_dataforseo_raw', array($this, 'handle_keyword_discovery_v2_dataforseo_raw'));
        add_action('wp_ajax_aysa_keyword_discovery_v2_competitor_gap_raw', array($this, 'handle_keyword_discovery_v2_competitor_gap_raw'));
        add_action('wp_ajax_aysa_keyword_mapping_v1_workspace', array($this, 'handle_keyword_mapping_v1_workspace'));
        add_action('wp_ajax_aysa_keyword_mapping_v1_save_selection', array($this, 'handle_keyword_mapping_v1_save_selection'));
        add_action('wp_ajax_aysa_missing_pages_v1_workspace', array($this, 'handle_missing_pages_v1_workspace'));
        add_action('wp_ajax_aysa_content_strategy_v1_workspace', array($this, 'handle_content_strategy_v1_workspace'));
        add_action('wp_ajax_aysa_competitor_gap_keywords_gap', array($this, 'handle_competitor_gap_keywords_gap'));
        add_action('wp_ajax_aysa_competitor_gap_add_to_keyword_research', array($this, 'handle_competitor_gap_add_to_keyword_research'));
        add_action('wp_ajax_aysa_competitor_identification_seed_competitors', array($this, 'handle_competitor_identification_seed_competitors'));
        add_action('wp_ajax_aysa_competitor_backlinks_setup_competitors', array($this, 'handle_competitor_backlinks_setup_competitors'));
        add_action('wp_ajax_aysa_content_strategy_generate', array($this, 'handle_content_strategy_generate'));
        add_action('wp_ajax_aysa_missing_pages_send_to_plan', array($this, 'handle_missing_pages_send_to_plan'));
        add_action('wp_ajax_aysa_missing_page_generate_meta', array($this, 'handle_missing_page_generate_meta'));
        add_action('wp_ajax_aysa_missing_pages_refactor_titles', array($this, 'handle_missing_pages_refactor_titles'));
        add_action('wp_ajax_aysa_technical_site_audit_start', array($this, 'handle_technical_site_audit_start'));
        add_action('wp_ajax_aysa_technical_site_audit_status', array($this, 'handle_technical_site_audit_status'));
        add_action('wp_ajax_aysa_technical_site_audit_results', array($this, 'handle_technical_site_audit_results'));
        add_action('admin_init', array($this, 'aysa_sf_site_audit_versions_maybe_create_table'));
        add_action('wp_ajax_aysa_technical_pagespeed_summary', array($this, 'handle_technical_pagespeed_summary'));
        add_action('wp_ajax_aysa_agentmarkup_check', array($this, 'handle_agentmarkup_check'));
        add_action('wp_ajax_aysa_technical_sitemap_robots_audit', array($this, 'handle_technical_sitemap_robots_audit'));
        add_action('wp_ajax_aysa_technical_sitemap_authority_gate', array($this, 'handle_technical_sitemap_authority_gate'));
        add_action('wp_ajax_aysa_technical_robots_save', array($this, 'handle_technical_robots_save'));
        add_action('wp_ajax_aysa_technical_robots_restore_default', array($this, 'handle_technical_robots_restore_default'));
        add_action('wp_ajax_aysa_technical_sitemap_generate', array($this, 'handle_technical_sitemap_generate'));
        add_action('wp_ajax_aysa_technical_schema_snapshot', array($this, 'handle_technical_schema_snapshot'));
        add_action('wp_ajax_aysa_technical_schema_refresh', array($this, 'handle_technical_schema_refresh'));
        add_action('wp_ajax_aysa_technical_schema_refresh_start', array($this, 'handle_technical_schema_refresh_start'));
        add_action('wp_ajax_aysa_technical_schema_refresh_status', array($this, 'handle_technical_schema_refresh_status'));
        add_action('wp_ajax_aysa_technical_schema_job_start', array($this, 'handle_technical_schema_job_start'));
        add_action('wp_ajax_aysa_technical_schema_job_status', array($this, 'handle_technical_schema_job_status'));
        add_action('wp_ajax_aysa_technical_schema_job_cancel', array($this, 'handle_technical_schema_job_cancel'));
        add_action('wp_ajax_aysa_technical_schema_rewrite_start', array($this, 'handle_technical_schema_rewrite_start'));
        add_action('wp_ajax_aysa_technical_schema_rewrite_status', array($this, 'handle_technical_schema_rewrite_status'));
        add_action('wp_ajax_aysa_schema_inventory_summary', array($this, 'handle_schema_inventory_summary'));
        add_action('wp_ajax_aysa_schema_inventory_list', array($this, 'handle_schema_inventory_list'));
        add_action('wp_ajax_aysa_schema_inventory_detail', array($this, 'handle_schema_inventory_detail'));
        add_action('wp_ajax_aysa_schema_inventory_auto_generate', array($this, 'handle_schema_inventory_auto_generate'));
        add_action('wp_ajax_aysa_schema_inventory_row_action', array($this, 'handle_schema_inventory_row_action'));
        add_action('wp_ajax_aysa_schema_inventory_row_update', array($this, 'handle_schema_inventory_row_update'));
        add_action('wp_ajax_aysa_schema_reconcile_start', array($this, 'handle_technical_schema_refresh_start'));
        add_action('wp_ajax_aysa_schema_reconcile_status', array($this, 'handle_technical_schema_refresh_status'));
        add_action('wp_ajax_aysa_internal_linking_workspace', array($this, 'handle_internal_linking_workspace'));
        add_action('wp_ajax_aysa_internal_linking_detail', array($this, 'handle_internal_linking_detail'));
        add_action('wp_ajax_aysa_internal_linking_start_run', array($this, 'handle_internal_linking_start_run'));
        add_action('wp_ajax_aysa_internal_linking_run_status', array($this, 'handle_internal_linking_run_status'));
        add_action('wp_ajax_aysa_internal_linking_row_action_v2', array($this, 'handle_internal_linking_row_action_v2'));
        add_action('aysa_process_schema_job_batch', array($this, 'process_technical_schema_job_event'), 10, 1);
        add_action('aysa_process_schema_rewrite_batch', array($this, 'process_technical_schema_rewrite_job_event'), 10, 1);
        add_action('aysa_schema_inventory_process_delta_event', array($this, 'handle_schema_inventory_delta_event'));
        add_action('aysa_schema_inventory_nightly_reconcile_event', array($this, 'handle_schema_inventory_nightly_reconcile_event'));
        add_action('init', array($this, 'aysa_schema_inventory_maybe_upgrade_table'));
        add_action('init', array($this, 'aysa_schema_inventory_ensure_scheduled_reconcile'));
        add_action('save_post', array($this, 'handle_schema_inventory_post_change'), 20, 3);
        add_action('transition_post_status', array($this, 'handle_schema_inventory_post_status_transition'), 20, 3);
        add_action('delete_post', array($this, 'handle_schema_inventory_post_delete'), 20, 1);
        add_action('created_term', array($this, 'handle_schema_inventory_term_change'), 20, 3);
        add_action('edited_term', array($this, 'handle_schema_inventory_term_change'), 20, 3);
        add_action('delete_term', array($this, 'handle_schema_inventory_term_delete'), 20, 5);
        add_action('template_redirect', array($this, 'maybe_serve_aysa_technical_sitemap'), 0);
        add_filter('robots_txt', array($this, 'filter_aysa_technical_robots_txt'), 20, 2);
        add_filter('wp_sitemaps_enabled', array($this, 'filter_aysa_technical_sitemap_provider_enabled'), 100);
        add_filter('wpseo_enable_xml_sitemap', array($this, 'filter_aysa_technical_sitemap_provider_enabled'), 100);
        add_filter('rank_math/sitemap/enabled', array($this, 'filter_aysa_technical_sitemap_provider_enabled'), 100);
        add_filter('wp_get_attachment_url', array($this, 'filter_aysa_image_optimization_attachment_url'), 20, 2);
        add_filter('wp_get_attachment_image_src', array($this, 'filter_aysa_image_optimization_attachment_src'), 20, 4);
        add_filter('wp_calculate_image_srcset', array($this, 'filter_aysa_image_optimization_srcset'), 20, 5);
        add_filter('the_content', array($this, 'filter_aysa_image_optimization_content_urls'), 20);
        add_action('aysa_keyword_discovery_refresh_background_event', array($this, 'handle_keyword_discovery_refresh_background_event'), 10, 1);
        add_action('admin_init', array($this, 'aysa_keyword_discovery_maybe_create_table'));
        add_filter('admin_body_class', array($this, 'add_admin_body_class'));
        
        // Register AJAX action for cumulative Research summary (all tabs)
        add_action('wp_ajax_aysa_get_research_summary', array($this, 'handle_get_research_summary'));
        
        // Register all the hooks related to the admin area functionality
        $this->register_hooks();
    }

    private function aysa_allow_destructive_tools() {
        return defined('AYSA_ALLOW_DESTRUCTIVE_TOOLS') && AYSA_ALLOW_DESTRUCTIVE_TOOLS;
    }

    private function aysa_guard_destructive_tool_request() {
        if ($this->aysa_allow_destructive_tools()) {
            return true;
        }

        wp_send_json_error('This maintenance tool is disabled in live mode.');
        return false;
    }

    public function add_admin_body_class($classes) {
        if ($this->is_aysa_admin_request()) {
            $classes .= ' aysa-admin-ui';
        }
        return $classes;
    }

    private function is_aysa_admin_request() {
        if (!is_admin()) {
            return false;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed callback inventory: this read-only/core lifecycle request is authenticated by WordPress or an enclosing guard and performs no unapproved write.
        $page = isset($_GET['page']) ? sanitize_key((string) wp_unslash($_GET['page'])) : '';
        if ($page !== '' && strpos($page, 'aysa') === 0) {
            return true;
        }
        $screen = function_exists('get_current_screen') ? get_current_screen() : null;
        if ($screen && isset($screen->id) && is_string($screen->id) && strpos($screen->id, 'aysa') !== false) {
            return true;
        }
        return false;
    }

    private function aysa_workspace_capability() {
        $capability = apply_filters('aysa_workspace_capability', 'manage_options');
        return is_string($capability) && $capability !== '' ? $capability : 'manage_options';
    }

    private function aysa_current_user_can_workspace() {
        return is_user_logged_in() && current_user_can($this->aysa_workspace_capability());
    }

    private function aysa_supported_research_tabs() {
        return array(
            'keyword-discovery',
            'competitor-gap',
            'competitor-identification',
            'competitor-backlinks',
            'missing-pages',
            'content-strategy',
            'sitemap-competition',
        );
    }

    private function aysa_normalize_research_tab($tab) {
        $tab = sanitize_key((string) $tab);
        if (in_array($tab, $this->aysa_supported_research_tabs(), true)) {
            return $tab;
        }

        return 'keyword-discovery';
    }

    private function aysa_last_research_tab_meta_key() {
        return 'aysa_last_research_tab';
    }

    private function aysa_last_keyword_discovery_subtab_meta_key() {
        return 'aysa_last_keyword_discovery_subtab';
    }

    private function aysa_setup_reset_forced_option_key() {
        return 'aysa_setup_reset_forced';
    }

    private function aysa_onboarding_run_token_option_key($project_id) {
        return 'aysa_onboarding_run_token_' . intval($project_id);
    }

    private function aysa_generate_onboarding_run_token() {
        try {
            return bin2hex(random_bytes(16));
        } catch (Exception $e) {
            return wp_generate_password(32, false, false);
        }
    }

    private function aysa_get_or_create_onboarding_run_token($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return '';
        }

        $token = trim((string) get_option($this->aysa_onboarding_run_token_option_key($project_id), ''));
        if ($token !== '') {
            return $token;
        }

        $token = $this->aysa_generate_onboarding_run_token();
        update_option($this->aysa_onboarding_run_token_option_key($project_id), $token, false);
        return $token;
    }

    private function aysa_rotate_onboarding_run_token($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return '';
        }

        $token = $this->aysa_generate_onboarding_run_token();
        update_option($this->aysa_onboarding_run_token_option_key($project_id), $token, false);
        return $token;
    }

    private function aysa_mark_setup_reset_forced() {
        update_option($this->aysa_setup_reset_forced_option_key(), '1', false);
    }

    private function aysa_clear_setup_reset_forced() {
        delete_option($this->aysa_setup_reset_forced_option_key());
    }

    private function aysa_is_setup_reset_forced() {
        return get_option($this->aysa_setup_reset_forced_option_key(), '') === '1';
    }

    private function aysa_get_last_research_tab() {
        $user_id = get_current_user_id();
        if ($user_id <= 0) {
            return 'keyword-discovery';
        }

        $saved = get_user_meta($user_id, $this->aysa_last_research_tab_meta_key(), true);
        return $this->aysa_normalize_research_tab($saved);
    }

    private function aysa_save_last_research_tab($tab) {
        $user_id = get_current_user_id();
        if ($user_id <= 0) {
            return '';
        }

        $normalized = $this->aysa_normalize_research_tab($tab);
        update_user_meta($user_id, $this->aysa_last_research_tab_meta_key(), $normalized);
        return $normalized;
    }

    private function aysa_supported_keyword_discovery_subtabs() {
        return array(
            'dashboard',
            'gsc_v2',
            'dataforseo_v2',
            'competitor_gap_v2',
            'gsc_auto_discovery',
            'keyword_suggestions',
            'mapped-keywords',
            'unmapped-keywords',
            'deleted-keywords',
            'banned-keywords',
        );
    }

    private function aysa_normalize_keyword_discovery_subtab($subtab) {
        $subtab = sanitize_key((string) $subtab);
        if (in_array($subtab, $this->aysa_supported_keyword_discovery_subtabs(), true)) {
            return $subtab;
        }

        return 'gsc_v2';
    }

    private function aysa_get_last_keyword_discovery_subtab() {
        $user_id = get_current_user_id();
        if ($user_id <= 0) {
            return 'gsc_v2';
        }

        $saved = get_user_meta($user_id, $this->aysa_last_keyword_discovery_subtab_meta_key(), true);
        return $this->aysa_normalize_keyword_discovery_subtab($saved);
    }

    private function aysa_save_last_keyword_discovery_subtab($subtab) {
        $user_id = get_current_user_id();
        if ($user_id <= 0) {
            return '';
        }

        $normalized = $this->aysa_normalize_keyword_discovery_subtab($subtab);
        update_user_meta($user_id, $this->aysa_last_keyword_discovery_subtab_meta_key(), $normalized);
        return $normalized;
    }

    private function aysa_last_research_tab_url() {
        return admin_url('admin.php?page=aysa-research&tab=' . rawurlencode($this->aysa_get_last_research_tab()));
    }

    public function maybe_disable_wc_admin_preload_on_aysa_pages() {
        if (!$this->is_aysa_admin_request()) {
            return;
        }

        // Prevent WooCommerce Admin REST preload from exhausting memory on AYSA screens.
        add_filter('woocommerce_component_settings_preload_endpoints', '__return_empty_array', 9999);
        add_filter('woocommerce_admin_preload_options', '__return_empty_array', 9999);
        add_filter('woocommerce_admin_preload_settings', '__return_empty_array', 9999);

        if (function_exists('wc_get_container') && class_exists('\\Automattic\\WooCommerce\\Blocks\\Assets\\AssetDataRegistry')) {
            try {
                $registry = wc_get_container()->get('\\Automattic\\WooCommerce\\Blocks\\Assets\\AssetDataRegistry');
                if ($registry && method_exists($registry, 'enqueue_asset_data')) {
                    remove_action('admin_print_footer_scripts', array($registry, 'enqueue_asset_data'), 1);
                }
            } catch (Throwable $e) {
                // Ignore container resolution errors in non-standard environments.
            }
        }
    }

    public function enqueue_styles() {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed callback inventory: this read-only/core lifecycle request is authenticated by WordPress or an enclosing guard and performs no unapproved write.
        $page = isset($_GET['page']) ? sanitize_key((string) wp_unslash($_GET['page'])) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed callback inventory: this read-only/core lifecycle request is authenticated by WordPress or an enclosing guard and performs no unapproved write.
        $tab = isset($_GET['tab']) ? sanitize_key((string) wp_unslash($_GET['tab'])) : '';

        $base_css_path = AYSA_PLUGIN_DIR . 'assets/css/aysa.css';
        $base_css_version = file_exists($base_css_path)
            ? (string) filemtime($base_css_path)
            : $this->version;
        wp_enqueue_style(
            $this->plugin_name,
            AYSA_PLUGIN_URL . 'assets/css/aysa.css',
            array(),
            $base_css_version,
            'all'
        );

        // Enqueue keyword discovery specific CSS only on that page
        $screen = get_current_screen();

        if (strpos((string) $page, 'aysa') === 0) {
            $debug_flag = (defined('AYSA_DEBUG') && AYSA_DEBUG) ? 'true' : 'false';
            $debug_js = "window.AYSA_DEBUG = {$debug_flag};\n" .
                "if (!window.AYSA_DEBUG && window.console) {\n" .
                "  window.console.log = function(){};\n" .
                "  window.console.debug = function(){};\n" .
                "  window.console.info = function(){};\n" .
                "}\n";
            wp_add_inline_script($this->plugin_name, $debug_js, 'before');
        }
        
        if (($page === 'aysa-research' && $tab === 'keyword-discovery') ||
            ($page === 'aysa-research' && empty($tab) && isset($_SERVER['HTTP_REFERER']) && strpos(sanitize_text_field(wp_unslash($_SERVER['HTTP_REFERER'])), 'keyword-discovery') !== false)) {
            $keyword_discovery_css_path = AYSA_PLUGIN_DIR . 'assets/css/keyword-discovery.css';
            $keyword_discovery_css_version = file_exists($keyword_discovery_css_path)
                ? (string) filemtime($keyword_discovery_css_path)
                : $this->version;
            
            wp_enqueue_style(
                $this->plugin_name . '-keyword-discovery',
                AYSA_PLUGIN_URL . 'assets/css/keyword-discovery.css',
                array(),
                $keyword_discovery_css_version,
                'all'
            );
        }

        if ($page === 'aysa-research') {
            $research_workspace_css_path = AYSA_PLUGIN_DIR . 'assets/css/research-workspace.css';
            $research_workspace_css_version = file_exists($research_workspace_css_path)
                ? (string) filemtime($research_workspace_css_path)
                : $this->version;

            wp_enqueue_style(
                $this->plugin_name . '-research-workspace',
                AYSA_PLUGIN_URL . 'assets/css/research-workspace.css',
                array($this->plugin_name),
                $research_workspace_css_version,
                'all'
            );
        }
        
        // Enqueue automation dashboard CSS
        if ($page === 'aysa-automation') {
            wp_enqueue_style(
                $this->plugin_name . '-automation',
                plugin_dir_url(__FILE__) . 'css/aysa-admin.css',
                array(),
                $this->version,
                'all'
            );
        }
        
        // Enqueue tab styles for settings page
        if ($page === 'aysa-settings') {
            wp_enqueue_style(
                $this->plugin_name . '-tabs',
                plugin_dir_url(__FILE__) . 'css/aysa-tabs.css',
                array(),
                $this->version,
                'all'
            );
        }

        if ($page === 'aysa-agent8-v3-lab') {
            $agent8v3_css_path = AYSA_PLUGIN_DIR . 'admin/css/aysa-agent8-v3-lab.css';
            $agent8v3_css_version = file_exists($agent8v3_css_path)
                ? substr(hash_file('sha256', $agent8v3_css_path), 0, 12)
                : $this->version;
            wp_enqueue_style(
                $this->plugin_name . '-agent8-v3-lab',
                AYSA_PLUGIN_URL . 'admin/css/aysa-agent8-v3-lab.css',
                array(),
                $agent8v3_css_version,
                'all'
            );
        }

        wp_enqueue_style('dashicons');

        // Enqueue UI core at the end so it can normalize legacy section styles.
        $ui_core_handle = $this->plugin_name . '-ui-core';
        $ui_core_deps = array($this->plugin_name);
        $ui_core_path = AYSA_PLUGIN_DIR . 'assets/css/aysa-ui-core.css';
        $ui_core_version = file_exists($ui_core_path)
            ? (string) filemtime($ui_core_path)
            : $this->version;
        wp_enqueue_style(
            $ui_core_handle,
            AYSA_PLUGIN_URL . 'assets/css/aysa-ui-core.css',
            $ui_core_deps,
            $ui_core_version,
            'all'
        );

        $legacy_research_tabs = array(
            'keyword-discovery',
            'competitor-gap',
            'competitor-identification',
            'competitor-backlinks',
            'missing-pages',
            'content-strategy',
            'sitemap-competition',
        );
        if ($page === 'aysa-research' && in_array($tab, $legacy_research_tabs, true)) {
            $legacy_reset_path = AYSA_PLUGIN_DIR . 'assets/css/aysa-legacy-admin-reset.css';
            $legacy_reset_version = file_exists($legacy_reset_path)
                ? (string) filemtime($legacy_reset_path)
                : $this->version;

            wp_enqueue_style(
                $this->plugin_name . '-legacy-admin-reset',
                AYSA_PLUGIN_URL . 'assets/css/aysa-legacy-admin-reset.css',
                array($ui_core_handle),
                $legacy_reset_version,
                'all'
            );
        }
        
        // Add custom CSS for highlighting SEO Profile menu item
        $custom_css = '
            #adminmenu .wp-submenu a[href*="aysa-setup-seo-profile"] {
                background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%) !important;
                color: white !important;
                font-weight: bold !important;
                border-radius: 4px !important;
                margin: 2px 0 !important;
                box-shadow: 0 2px 4px rgba(76, 175, 80, 0.3) !important;
                position: relative !important;
            }
            #adminmenu .wp-submenu a[href*="aysa-setup-seo-profile"]:hover {
                background: linear-gradient(135deg, #45a049 0%, #4CAF50 100%) !important;
                color: white !important;
                transform: translateX(2px) !important;
                transition: all 0.2s ease !important;
            }
            #adminmenu .wp-submenu a[href*="aysa-setup-seo-profile"]:before {
                content: "" !important;
                position: absolute !important;
                left: -8px !important;
                top: 50% !important;
                transform: translateY(-50%) !important;
                width: 4px !important;
                height: 20px !important;
                background: #FFD700 !important;
                border-radius: 2px !important;
            }
        ';
        wp_add_inline_style($this->plugin_name, $custom_css);
    }

    public function enqueue_scripts() {
        /**
         * This function is provided for demonstration purposes only.
         *
         * An instance of this class should be passed to the run() function
         * defined in Aysa_Loader as all of the hooks are defined
         * in that particular class.
         *
         * The Aysa_Loader will then create the relationship
         * between the defined hooks and the functions defined in this
         * class.
         */

        $screen = function_exists('get_current_screen') ? get_current_screen() : null;
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed callback inventory: this read-only/core lifecycle request is authenticated by WordPress or an enclosing guard and performs no unapproved write.
        $page = isset($_GET['page']) ? sanitize_key((string) wp_unslash($_GET['page'])) : '';
        $is_aysa_page = (strpos($page, 'aysa') === 0)
            || ($screen && isset($screen->id) && is_string($screen->id) && strpos($screen->id, 'aysa') !== false);

        $is_supported_editor = false;
        if ($screen && isset($screen->base) && is_string($screen->base)) {
            if ($screen->base === 'post' && isset($screen->post_type)) {
                $is_supported_editor = in_array((string) $screen->post_type, array('post', 'page', 'product'), true);
            } elseif ($screen->base === 'term') {
                // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed callback inventory: this read-only/core lifecycle request is authenticated by WordPress or an enclosing guard and performs no unapproved write.
                $taxonomy = isset($_GET['taxonomy']) ? sanitize_key((string) wp_unslash($_GET['taxonomy'])) : '';
                $is_supported_editor = in_array($taxonomy, array('category', 'product_cat'), true);
            }
        }

        if (!$is_aysa_page && !$is_supported_editor) {
            return;
        }

        // Get project ID from WordPress options (no hardcoded default)
        $project_id = intval(get_option('aysa_project_id', 0));

        // Main plugin script
        wp_enqueue_script(
            $this->plugin_name,
            AYSA_PLUGIN_URL . 'assets/js/aysa.js',
            array('jquery'),
            $this->version,
            false
        );
        
        // API service script
        wp_enqueue_script(
            $this->plugin_name . '-api',
            AYSA_PLUGIN_URL . 'assets/js/aysa-api.js',
            array('jquery', $this->plugin_name),
            $this->version . '.1',
            false
        );
        
        // Credit tracker script - monitors API calls and updates credits in real-time
        wp_enqueue_script(
            $this->plugin_name . '-credit-tracker',
            AYSA_PLUGIN_URL . 'assets/js/aysa-credit-tracker.js',
            array('jquery', $this->plugin_name . '-api'),
            $this->version,
            false
        );
        
        // Service type helper script for VAT calculations and country/region handling
        wp_enqueue_script(
            $this->plugin_name . '-service-type-helper',
            AYSA_PLUGIN_URL . 'admin/js/aysa-service-type-helper.js',
            array('jquery'),
            $this->version,
            false
        );

        wp_enqueue_script(
            'aysa-domain-authority-scheduler',
            AYSA_PLUGIN_URL . 'admin/js/aysa-domain-authority-scheduler.js',
            array('jquery'),
            $this->version,
            true
        );

        $automation_config = get_option('aysa_automation_config', array());
        $automation_config['ajaxurl'] = admin_url('admin-ajax.php');
        $automation_config['nonce'] = wp_create_nonce('aysa_automation_nonce');
        wp_localize_script(
            'aysa-domain-authority-scheduler',
            'aysaAutomationConfig',
            $automation_config
        );

        $api_js_config = Aysa_API_Config::get_js_config();

        wp_localize_script(
            $this->plugin_name,
            'aysa_params',
            array(
                'api_base_url' => AYSA_API_BASE_URL,
                'ajax_url' => admin_url('admin-ajax.php'),
                'nonce' => wp_create_nonce('aysa_nonce'),
                'project_id' => $project_id,
                'automation_config' => get_option('aysa_automation_config', array()),
                'api_config' => $api_js_config
            )
        );

        // Always expose API config for assets/js/aysa-api.js across all admin screens.
        wp_localize_script(
            $this->plugin_name . '-api',
            'aysaApiConfig',
            $api_js_config
        );

        
        // Add the WordPress AJAX URL and nonce to our script
        wp_localize_script(
            $this->plugin_name,
            'aysa_ajax',
            array(
                'ajax_url' => admin_url('admin-ajax.php'),
                'nonce'    => wp_create_nonce('aysa_nonce'),
                'project_id' => $project_id,
                'api_config' => $api_js_config
            )
        );

        wp_localize_script(
            $this->plugin_name . '-credit-tracker',
            'aysaCreditTrackerParams',
            array(
                'ajax_url' => admin_url('admin-ajax.php'),
                'nonce' => wp_create_nonce('aysa_nonce'),
                'project_id' => $project_id,
            )
        );
        
        // Add link purchase nonce for AJAX requests
        wp_add_inline_script(
            $this->plugin_name,
            'var aysaLinkPurchaseNonce = "' . wp_create_nonce('aysa_link_purchase_nonce') . '";',
            'before'
        );
        
        // Check if we're on specific AYSA admin pages to add page-specific parameters
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed callback inventory: this read-only/core lifecycle request is authenticated by WordPress or an enclosing guard and performs no unapproved write.
        $page = isset($_GET['page']) ? sanitize_key((string) wp_unslash($_GET['page'])) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed callback inventory: this read-only/core lifecycle request is authenticated by WordPress or an enclosing guard and performs no unapproved write.
        $tab = isset($_GET['tab']) ? sanitize_key((string) wp_unslash($_GET['tab'])) : '';
        
        // Define common parameters that all AYSA pages need
        $common_params = array(
            'api_base_url' => AYSA_API_BASE_URL,
            'ajax_url' => admin_url('admin-ajax.php'),
            'admin_url' => admin_url(),
            'nonce' => wp_create_nonce('aysa_metabox_nonce'),
            'ajax_nonce' => wp_create_nonce('aysa-ajax-nonce'),
            'generic_nonce' => wp_create_nonce('aysa_nonce'),
            'credit_analytics_nonce' => wp_create_nonce('aysa_credit_analytics_nonce'),
            'project_id' => $project_id,
            'site_url' => site_url(),
            'home_url' => home_url(),
            'api_config' => $api_js_config
        );

        if ($page === 'aysa-agent8-v3-lab') {
            $agent8v3_js_path = AYSA_PLUGIN_DIR . 'admin/js/aysa-agent8-v3-lab.js';
            $agent8v3_js_version = file_exists($agent8v3_js_path)
                ? substr(hash_file('sha256', $agent8v3_js_path), 0, 12)
                : $this->version;
            $agent8v3_project_id = $project_id > 0 ? 'lab_' . $project_id : 'lab_test';
            wp_enqueue_script(
                $this->plugin_name . '-agent8-v3-lab',
                AYSA_PLUGIN_URL . 'admin/js/aysa-agent8-v3-lab.js',
                array('jquery'),
                $agent8v3_js_version,
                true
            );
            wp_localize_script(
                $this->plugin_name . '-agent8-v3-lab',
                'aysaAgent8V3Lab',
                array(
                    'ajax_url' => admin_url('admin-ajax.php'),
                    'nonce' => wp_create_nonce('aysa_nonce'),
                    'project_id' => $agent8v3_project_id,
                    'site_host' => (string) wp_parse_url(home_url('/'), PHP_URL_HOST),
                    'interface_language' => aysa_get_interface_language(),
                    'factory_reset_allowed_targets' => $this->aysa_agent8_factory_reset_allowed_targets(),
                    'qa_enabled' => $this->aysa_agent8v3_qa_enabled(),
                    'release' => function_exists('aysa_release_metadata') ? aysa_release_metadata() : array(
                        'plugin_version' => defined('AYSA_VERSION') ? AYSA_VERSION : $this->version,
                    ),
                )
            );
        }
        
        // Enqueue Content Strategy script when on content-strategy tab
        if ($page === 'aysa-research' && $tab === 'content-strategy') {
            wp_enqueue_script(
                'aysa-content-strategy',
                AYSA_PLUGIN_URL . 'admin/js/aysa-content-strategy.js',
                array('jquery'),
                $this->version,
                true
            );
            
            // Add nonce for Content Strategy
            wp_add_inline_script(
                'aysa-content-strategy',
                'var aysaContentStrategyNonce = "' . wp_create_nonce('aysa_content_strategy_nonce') . '";',
                'before'
            );
            
            aysa_debug_log('[AYSA] Content Strategy script enqueued');
        }
        
        // Add page-specific parameters and scripts
        if ($page === 'aysa-on-page-optimization') {
            $aysa_actions_version = @filemtime(AYSA_PLUGIN_DIR . 'admin/js/aysa-actions.js') ?: $this->version;

            // Enqueue aysa-actions script for On-Page Optimization page
            wp_enqueue_script(
                'aysa-actions',
                AYSA_PLUGIN_URL . 'admin/js/aysa-actions.js',
                array('jquery'),
                $aysa_actions_version,
                true
            );
            
            // Localize aysa-actions script
            wp_localize_script(
                'aysa-actions',
                'aysaParams',
                array(
                    'ajax_url' => admin_url('admin-ajax.php'),
                    'admin_url' => admin_url(),
                    'nonce' => wp_create_nonce('aysa-ajax-nonce'),
                    'api_base_url' => AYSA_API_BASE_URL,
                    'site_url' => get_site_url(),
                    'api_config' => Aysa_API_Config::get_js_config()
                )
            );
            
            // Enqueue SEO history script for On-Page Optimization page
            wp_enqueue_script(
                'aysa-seo-history',
                AYSA_PLUGIN_URL . 'assets/js/aysa-seo-history.js',
                array('jquery'),
                $this->version,
                true
            );
            
            // Localize SEO history script with AJAX data
            wp_localize_script(
                'aysa-seo-history',
                'aysaSeoHistoryAjax',
                array(
                    'ajax_url' => admin_url('admin-ajax.php'),
                    'nonce' => wp_create_nonce('aysa_seo_history_nonce'),
                    'user_id' => get_current_user_id(),
                    'user_name' => wp_get_current_user()->display_name,
                    'site_url' => get_site_url()
                )
            );
            
            $aysa_generator_core_version = @filemtime(AYSA_PLUGIN_DIR . 'assets/js/aysa-seo-generator-core.js') ?: $this->version;

            // Enqueue aysa-seo-generator-core script with aysa-actions dependency
            wp_enqueue_script(
                'aysa-seo-generator-core',
                AYSA_PLUGIN_URL . 'assets/js/aysa-seo-generator-core.js',
                array('jquery', 'aysa-actions'),
                $aysa_generator_core_version,
                true
            );
            
            // Enqueue script for On-Page Optimization page
            $on_page_js_version = $this->version;
            $on_page_js_file = plugin_dir_path(__FILE__) . 'js/aysa-on-page-optimization-v2.js';
            if (file_exists($on_page_js_file)) {
                $on_page_js_version = (string) filemtime($on_page_js_file);
            }
            wp_enqueue_script(
                $this->plugin_name . '-on-page-optimization',
                AYSA_PLUGIN_URL . 'admin/js/aysa-on-page-optimization-v2.js',
                array('jquery'),
                $on_page_js_version,
                true
            );
            
            // Pass specific parameters for on-page optimization scripts
            wp_localize_script(
                $this->plugin_name . '-on-page-optimization',
                'aysa_params',
                array_merge($common_params, array(
                    'wc_product_taxonomy' => 'product_cat',
                    'wc_product_post_type' => 'product'
                ))
            );
        }

        // Ensure WordPress visual editor assets are available for Off-Page article modal
        if ($page === 'aysa-off-page-optimization' && function_exists('wp_enqueue_editor')) {
            wp_enqueue_editor();
        }
        
        // Add parameters for monitoring analytics page
        if ($page === 'aysa-monitoring-analytics') {
            wp_localize_script(
                $this->plugin_name,
                'aysaParams',
                $common_params
            );
        }
        
        // Add parameters for technical optimization page
        if ($page === 'aysa-technical-optimization') {
            $agentmarkup_js_path = AYSA_PLUGIN_DIR . 'admin/js/aysa-agentmarkup-checker.js';
            $agentmarkup_css_path = AYSA_PLUGIN_DIR . 'admin/css/aysa-agentmarkup-checker.css';
            wp_enqueue_script(
                'aysa-agentmarkup-checker',
                AYSA_PLUGIN_URL . 'admin/js/aysa-agentmarkup-checker.js',
                array(),
                file_exists($agentmarkup_js_path) ? substr(hash_file('sha256', $agentmarkup_js_path), 0, 12) : $this->version,
                true
            );
            wp_enqueue_style(
                'aysa-agentmarkup-checker',
                AYSA_PLUGIN_URL . 'admin/css/aysa-agentmarkup-checker.css',
                array(),
                file_exists($agentmarkup_css_path) ? substr(hash_file('sha256', $agentmarkup_css_path), 0, 12) : $this->version
            );
            wp_localize_script(
                'aysa-agentmarkup-checker',
                'aysaAgentMarkupChecker',
                array(
                    'ajaxUrl' => admin_url('admin-ajax.php'),
                    'nonce' => wp_create_nonce('aysa_nonce'),
                    'siteUrl' => home_url('/'),
                    'interfaceLanguage' => function_exists('aysa_get_interface_language') ? aysa_get_interface_language() : 'en',
                )
            );

            // Schema Generator script - DISABLED (using simple modal approach)
            // wp_enqueue_script(
            //     'aysa-schema-generator',
            //     plugin_dir_url(__FILE__) . 'js/aysa-schema-generator.js',
            //     array('jquery', $this->plugin_name),
            //     $this->version,
            //     true
            // );
            
            // Schema Frontend Generator - new implementation
            wp_enqueue_script(
                'aysa-schema-frontend',
                plugin_dir_url(__FILE__) . 'js/aysa-schema-frontend.js',
                array('jquery'),
                $this->version,
                true
            );
            
            // Get company information from SEO profile
            // Try multiple option keys for company data
            $company_name = get_option('aysa_company_name', 
                               get_option('company_name', 
                                 get_bloginfo('name')));
            $company_description = get_option('aysa_company_description', 
                                     get_option('company_description',
                                       get_bloginfo('description')));
            
            // Localize script for AJAX
            wp_localize_script(
                'aysa-schema-frontend',
                'aysaSchemaAjax',
                array(
                    'ajaxurl' => admin_url('admin-ajax.php'),
                    'nonce' => wp_create_nonce('aysa_schema_nonce'),
                    'siteUrl' => home_url(),
                    'siteName' => $company_name,
                    'companyName' => $company_name,
                    'companyDescription' => $company_description,
                    'logoUrl' => get_site_icon_url() ? get_site_icon_url() : (home_url() . '/wp-content/uploads/logo.png')
                )
            );
            
            // Schema CSS removed - using inline styles for simple modal
            
            // Internal Linking script for technical optimization
            wp_enqueue_script(
                'aysa-internal-linking',
                plugin_dir_url(__FILE__) . 'js/aysa-internal-linking.js',
                array('jquery', $this->plugin_name),
                file_exists(plugin_dir_path(__FILE__) . 'js/aysa-internal-linking.js') ? filemtime(plugin_dir_path(__FILE__) . 'js/aysa-internal-linking.js') : $this->version,
                true
            );
            
            // Add nonce for internal linking AJAX calls
            wp_localize_script(
                'aysa-internal-linking',
                'aysaInternalLinking',
                array(
                    'ajaxUrl' => admin_url('admin-ajax.php'),
                    'nonce' => wp_create_nonce('aysa_internal_linking_nonce')
                )
            );
            
            wp_localize_script(
                $this->plugin_name,
                'aysaParams',
                $common_params
            );
        }
        
        // Add parameters for settings page, automation page, and adjustment improvement page (for automation tab)
        if ($page === 'aysa-settings' || $page === 'aysa-automation' || $page === 'aysa-adjustment-improvement') {
            // Enqueue tab functionality for settings page
            if ($page === 'aysa-settings') {
                wp_enqueue_script(
                    'aysa-tabs',
                    plugin_dir_url(__FILE__) . 'js/aysa-tabs.js',
                    array('jquery'),
                    $this->version,
                    true
                );
            }
            
            // Enqueue automation controls script for settings and automation pages
            wp_enqueue_script(
                'aysa-automation-controls',
                plugin_dir_url(__FILE__) . 'js/aysa-automation-controls.js',
                array('jquery', $this->plugin_name),
                $this->version,
                false
            );
            
            // Internal Linking script for technical optimization
            wp_enqueue_script(
                'aysa-internal-linking',
                plugin_dir_url(__FILE__) . 'js/aysa-internal-linking.js',
                array('jquery', $this->plugin_name),
                file_exists(plugin_dir_path(__FILE__) . 'js/aysa-internal-linking.js') ? filemtime(plugin_dir_path(__FILE__) . 'js/aysa-internal-linking.js') : $this->version,
                true
            );
            
            wp_localize_script(
                $this->plugin_name,
                'aysaParams',
                $common_params
            );
            
            wp_localize_script(
                'aysa-automation-controls',
                'aysaAutomation',
                array(
                    'nonce' => wp_create_nonce('aysa_automation_nonce'),
                    'ajax_url' => admin_url('admin-ajax.php')
                )
            );
        }
        
        // Add parameters for all other AYSA pages
        if ($page === 'aysa' || strpos($page, 'aysa-') === 0) {
            // Some third-party admin pointers (e.g. Elementor) can run on AYSA pages.
            // Ensure the WP pointer script is available to avoid runtime errors.
            wp_enqueue_script('wp-pointer');
            wp_enqueue_style('wp-pointer');

            wp_localize_script(
                $this->plugin_name,
                'aysaParams',
                $common_params
            );
        }

        // Script to hide the 'Welcome to aysa' submenu item
        $custom_script = "
            document.addEventListener('DOMContentLoaded', function() {
                var menuLinks = document.querySelectorAll('#adminmenu a[href=\"admin.php?page=aysa-welcome\"]');
                menuLinks.forEach(function(link) {
                    var parentLi = link.closest('li');
                    if (parentLi) {
                        parentLi.style.display = 'none';
                    }
                });
            });
        ";
        wp_add_inline_script($this->plugin_name, $custom_script);
    }

    public function add_plugin_admin_menu() {
        $key_validation = null;
        $has_valid_access_key = $this->aysa_is_workspace_activated_for_admin_menu($key_validation);

        $setup_completed = $this->aysa_is_setup_profile_completed_effectively();
        $apprentice_mode = $this->aysa_is_apprentice_mode_enabled();
        $show_novice_only_menu = $apprentice_mode && $setup_completed;

        $show_legacy_admin_menus = defined('AYSA_SHOW_LEGACY_ADMIN_MENUS') && AYSA_SHOW_LEGACY_ADMIN_MENUS;
        $root_callback = !$has_valid_access_key
            ? array($this, 'display_activation_page')
            : array($this, 'display_agent8_v3_lab_page');

        add_menu_page(
            aysa_interface_text('AYSA AI SEO - Asistent', 'AYSA AI SEO - Assistant'),
            'AYSA AI SEO',
            $this->aysa_workspace_capability(),
            'aysa',
            $root_callback,
            'dashicons-speedometer',
            6
        );

        // Keep critical direct routes registered even when access-key validation is temporarily unavailable.
        add_submenu_page(
            null,
            aysa_interface_text('Optimizare tehnică', 'Technical Optimization'),
            aysa_interface_text('Optimizare tehnică', 'Technical Optimization'),
            $this->aysa_workspace_capability(),
            'aysa-technical-optimization',
            array($this, 'display_technical_optimization_page')
        );

        if (!$has_valid_access_key) {
            add_submenu_page(
                null,
                aysa_interface_text('Cheie de activare', 'Activation Key'),
                aysa_interface_text('Cheie de activare', 'Activation Key'),
                $this->aysa_workspace_capability(),
                'aysa-activation-key',
                array($this, 'display_activation_page')
            );
            add_submenu_page(
                null,
                aysa_interface_text('AYSA Agent 8', 'AYSA Agent 8'),
                aysa_interface_text('AYSA Agent 8', 'AYSA Agent 8'),
                $this->aysa_workspace_capability(),
                'aysa-agent8-v3-lab',
                array($this, 'display_agent8_v3_lab_page')
            );
            remove_submenu_page('aysa', 'aysa');
            return;
        }

        add_submenu_page(
            'aysa',
            aysa_interface_text('AYSA Agent 8', 'AYSA Agent 8'),
            aysa_interface_text('AYSA Agent 8', 'AYSA Agent 8'),
            $this->aysa_workspace_capability(),
            'aysa-agent8-v3-lab',
            array($this, 'display_agent8_v3_lab_page')
        );

        if ($this->aysa_agent8v3_qa_enabled()) {
            add_submenu_page(
                'aysa',
                aysa_interface_text('Probleme beta Agent8', 'Agent8 Beta Issues'),
                aysa_interface_text('Probleme beta Agent8', 'Agent8 Beta Issues'),
                $this->aysa_workspace_capability(),
                'aysa-agent8-beta-issues',
                array($this, 'display_agent8_beta_issues_page')
            );
        }

        if ($show_legacy_admin_menus) {
            add_submenu_page(
                'aysa',
                aysa_interface_text('Funcții vechi', 'Legacy'),
                aysa_interface_text('Funcții vechi', 'Legacy'),
                $this->aysa_workspace_capability(),
                'aysa-legacy',
                array($this, 'display_legacy_page')
            );
        }

        // Keep direct routes registered, but expose them through Legacy only.
        add_submenu_page(
            null,
            aysa_interface_text('Configurare profil SEO', 'SEO Profile Setup'),
            aysa_interface_text('Configurare profil SEO', 'SEO Profile Setup'),
            $this->aysa_workspace_capability(),
            'aysa-setup-seo-profile',
            array($this, 'display_setup_seo_profile_page')
        );

        // Keep activation route registered for direct access and QA even after a valid key exists.
        add_submenu_page(
            null,
            aysa_interface_text('Cheie de activare', 'Activation Key'),
            aysa_interface_text('Cheie de activare', 'Activation Key'),
            $this->aysa_workspace_capability(),
            'aysa-activation-key',
            array($this, 'display_activation_page')
        );

        if ($show_novice_only_menu) {
            add_submenu_page(
                null,
                aysa_interface_text('Cheie de activare', 'Activation Key'),
                aysa_interface_text('Cheie de activare', 'Activation Key'),
                $this->aysa_workspace_capability(),
                'aysa-activation-key',
                array($this, 'display_activation_page')
            );
        } else {
            add_submenu_page(
                null,
                aysa_interface_text('Panou principal', 'Dashboard'),
                aysa_interface_text('Panou principal', 'Dashboard'),
                $this->aysa_workspace_capability(),
                'aysa',
                array($this, 'display_dashboard_page')
            );
        }

        add_submenu_page(
            null,
            aysa_interface_text('Cercetare și analiză', 'Research & Analysis'),
            aysa_interface_text('Cercetare și analiză', 'Research & Analysis'),
            $this->aysa_workspace_capability(),
            'aysa-research',
            array($this, 'display_plugin_admin_page')
        );
        
        add_submenu_page(
            null,
            aysa_interface_text('Optimizare On-Page', 'On-Page Optimization'),
            aysa_interface_text('Optimizare On-Page', 'On-Page Optimization'),
            $this->aysa_workspace_capability(),
            'aysa-on-page-optimization',
            array($this, 'display_on_page_optimization_page')
        );
        
        add_submenu_page(
            null,
            aysa_interface_text('Optimizare Off-Page', 'Off-Page Optimization'),
            aysa_interface_text('Optimizare Off-Page', 'Off-Page Optimization'),
            $this->aysa_workspace_capability(),
            'aysa-off-page-optimization',
            array($this, 'display_off_page_optimization_page')
        );

        add_submenu_page(
            null,
            aysa_interface_text('Monitorizare și analiză', 'Monitoring & Analytics'),
            aysa_interface_text('Monitorizare și analiză', 'Monitoring & Analytics'),
            $this->aysa_workspace_capability(),
            'aysa-monitoring-analytics',
            array($this, 'display_monitoring_analytics_page')
        );
        
        /*
        add_submenu_page(
            'aysa',
            'Omnichannel SEO',
            '⟲ Omnichannel SEO',
            'manage_options',
            'aysa-omnichannel-seo',
            array($this, 'display_omnichannel_seo_page')
        );
        */
        
        // SEO Profile Setup moved to top of menu

        // Add Welcome to Aysa page - Keep it registered for direct access
        add_submenu_page(
            null, // Parent slug
            aysa_interface_text('Bun venit la AYSA', 'Welcome to AYSA'), // Page title
            aysa_interface_text('Bun venit la AYSA', 'Welcome to AYSA'), // Menu title (will be hidden by CSS/JS)
            $this->aysa_workspace_capability(),
            'aysa-welcome', // Menu slug
            array($this, 'display_welcome_aysa_page') // Function to display page
        );

        add_submenu_page(
            null,
            aysa_interface_text('Setări', 'Settings'),
            aysa_interface_text('Setări', 'Settings'),
            $this->aysa_workspace_capability(),
            'aysa-settings',
            array($this, 'display_settings_page')
        );

        remove_submenu_page('aysa', 'aysa');

        // Keep all plugin areas visible while we stabilize the orchestrator architecture.
        
        // Automation moved to Adjustment & Improvement as a tab
        /*
        add_submenu_page(
            'aysa',
            'Automation',
            'Automation',
            'manage_options',
            'aysa-automation',
            array($this, 'display_automation_page')
        );
        */


    }

    private function aysa_hide_submenus_until_setup_complete() {
        add_action('admin_head', function () {
            echo '<style>
                #adminmenu .wp-submenu a[href*="page=aysa-research"],
                #adminmenu .wp-submenu a[href*="page=aysa-technical-optimization"],
                #adminmenu .wp-submenu a[href*="page=aysa-on-page-optimization"],
                #adminmenu .wp-submenu a[href*="page=aysa-off-page-optimization"],
                #adminmenu .wp-submenu a[href*="page=aysa-monitoring-analytics"],
                #adminmenu .wp-submenu a[href*="page=aysa-adjustment-improvement"],
                #adminmenu .wp-submenu a[href*="page=aysa-settings"],
                #adminmenu .wp-submenu a[href*="page=aysa-welcome"],
                #adminmenu .wp-submenu a[href="admin.php?page=aysa"] {
                    display: none !important;
                }
            </style>';
        }, 1000);
    }

    private function aysa_is_apprentice_mode_enabled() {
        $raw = get_option('aysa_apprentice_mode', '1');
        return !in_array((string) $raw, array('0', 'false', 'off'), true);
    }

    /**
     * Display the dashboard page with login
     */
    public function display_dashboard_page() {
        $auth_manager = Aysa_Auth_Manager::get_instance(); $auth_status = $auth_manager->is_authenticated(); $is_logged_in = $auth_status['authenticated'];
        if (!$is_logged_in) {
            $aysa_section_title = 'Dashboard';
            include AYSA_PLUGIN_DIR . 'admin/partials/aysa-login-required.php';
            return;
        }
        if ($this->aysa_is_apprentice_mode_enabled() && $this->aysa_is_setup_profile_completed_effectively()) {
            $this->display_agent8_v3_lab_page();
            return;
        }
        $user_id = $auth_status['aysa_user_id'];
        $aysa_user_id = $user_id;
        include_once AYSA_PLUGIN_DIR . 'admin/partials/aysa-dashboard.php';
    }

    // Check authentication before displaying any admin page
    private function check_authentication($unused = true) {
        $auth_manager = Aysa_Auth_Manager::get_instance(); $auth_status = $auth_manager->is_authenticated(); $is_logged_in = $auth_status['authenticated'];
        if (!$is_logged_in) {
            // Nu mai includ direct pagina de login aici
            return false;
        }
        $validation = null;
        if (!$this->aysa_is_workspace_activated_for_admin_menu($validation)) {
            return false;
        }
        return true;
    }

    public function enforce_access_key_for_admin_pages() {
        if (!is_admin() || wp_doing_ajax()) {
            return;
        }
        if (!$this->aysa_current_user_can_workspace()) {
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed callback inventory: this read-only/core lifecycle request is authenticated by WordPress or an enclosing guard and performs no unapproved write.
        $page = isset($_GET['page']) ? sanitize_key((string) wp_unslash($_GET['page'])) : '';
        if ($page === '' || strpos($page, 'aysa') !== 0) {
            return;
        }

        $validation = null;
        if ($this->aysa_is_workspace_activated_for_admin_menu($validation)) {
            return;
        }

        if ($page === 'aysa') {
            wp_safe_redirect(admin_url('admin.php?page=aysa-agent8-v3-lab'));
            exit;
        }

        // Allow the conversational onboarding/agent pages without a valid key.
        if ($page === 'aysa-activation-key' || $page === 'aysa-ai-agent' || $page === 'aysa-agent8' || $page === 'aysa-agent9' || $page === 'aysa-agent8-v3-lab') {
            return;
        }

        $msg = is_array($validation) && !empty($validation['message'])
            ? (string) $validation['message']
            : 'Access key is required.';
        wp_die(
            '<h1>AYSA Access Blocked</h1><p>' . esc_html($msg) . '</p><p>Configure a valid access key first.</p>',
            'AYSA Access Blocked',
            array('response' => 403)
        );
    }

    public function redirect_deprecated_agent_pages() {
        if (!is_admin() || wp_doing_ajax()) {
            return;
        }
        if (!$this->aysa_current_user_can_workspace()) {
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed callback inventory: this read-only/core lifecycle request is authenticated by WordPress or an enclosing guard and performs no unapproved write.
        $page = isset($_GET['page']) ? sanitize_key((string) wp_unslash($_GET['page'])) : '';
        if (!in_array($page, array('aysa-ai-agent', 'aysa-agent7', 'aysa-agent8', 'aysa-agent9'), true)) {
            return;
        }

        wp_safe_redirect(admin_url('admin.php?page=aysa-agent8-v3-lab'));
        exit;
    }

    public function enforce_setup_gate_for_admin_pages() {
        if (!is_admin() || wp_doing_ajax()) {
            return;
        }
        if (!$this->aysa_current_user_can_workspace()) {
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed callback inventory: this read-only/core lifecycle request is authenticated by WordPress or an enclosing guard and performs no unapproved write.
        $page = isset($_GET['page']) ? sanitize_key((string) wp_unslash($_GET['page'])) : '';
        if ($page === '' || strpos($page, 'aysa') !== 0) {
            return;
        }

        $validation = null;
        if (!$this->aysa_is_workspace_activated_for_admin_menu($validation)) {
            // Access-key gate handles this case and will block with 403.
            return;
        }

        if ($page === 'aysa-activation-key' || $page === 'aysa-agent8' || $page === 'aysa-agent9') {
            return;
        }

        $setup_reason = array();
        $setup_completed = $this->aysa_is_setup_profile_completed_effectively($setup_reason);
        $apprentice_mode = $this->aysa_is_apprentice_mode_enabled();
        if (!$setup_completed && $apprentice_mode) {
            if ($page === 'aysa') {
                wp_safe_redirect(admin_url('admin.php?page=aysa-agent8-v3-lab'));
                exit;
            }
            return;
        }
        if (!$setup_completed && $page !== 'aysa-setup-seo-profile') {
            $reason_code = isset($setup_reason['code']) ? sanitize_key((string) $setup_reason['code']) : '';
            if ($reason_code === 'google_not_connected') {
                $this->setup_gate_notice = array(
                    'type' => 'warning',
                    'message' => isset($setup_reason['message']) ? (string) $setup_reason['message'] : __('Google connection is not completed. Some features may be limited until it is connected.', 'aysaaiseo'),
                    'step' => 'integrations',
                );
                return;
            }
            $target_step = isset($setup_reason['step']) ? sanitize_key((string) $setup_reason['step']) : 'general';
            if ($target_step === '') {
                $target_step = 'general';
            }
            $target_url = add_query_arg(
                array(
                    'page' => 'aysa-setup-seo-profile',
                    'step' => $target_step,
                    'aysa_setup_error' => isset($setup_reason['code']) ? sanitize_key((string) $setup_reason['code']) : 'setup_incomplete',
                ),
                admin_url('admin.php')
            );
            wp_safe_redirect($target_url);
            exit;
        }

        if ($setup_completed && $page === 'aysa') {
            $target = $apprentice_mode
                ? admin_url('admin.php?page=aysa-agent8-v3-lab')
                : $this->aysa_last_research_tab_url();
            wp_safe_redirect($target);
            exit;
        }
    }

    public function render_setup_gate_notice() {
        if (!is_admin() || !$this->aysa_current_user_can_workspace()) {
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed callback inventory: this read-only/core lifecycle request is authenticated by WordPress or an enclosing guard and performs no unapproved write.
        $page = isset($_GET['page']) ? sanitize_key((string) wp_unslash($_GET['page'])) : '';
        if ($page === '' || strpos($page, 'aysa') !== 0) {
            return;
        }

        if (empty($this->setup_gate_notice) || !is_array($this->setup_gate_notice)) {
            $setup_reason = array();
            $setup_completed = $this->aysa_is_setup_profile_completed_effectively($setup_reason);
            $reason_code = isset($setup_reason['code']) ? sanitize_key((string) $setup_reason['code']) : '';
            if ($setup_completed || $reason_code !== 'google_not_connected') {
                return;
            }
            $this->setup_gate_notice = array(
                'type' => 'warning',
                'message' => isset($setup_reason['message']) ? (string) $setup_reason['message'] : __('Google connection is not completed. Some features may be limited until it is connected.', 'aysaaiseo'),
                'step' => 'integrations',
            );
        }

        $type = isset($this->setup_gate_notice['type']) ? sanitize_html_class((string) $this->setup_gate_notice['type']) : 'warning';
        $message = isset($this->setup_gate_notice['message']) ? (string) $this->setup_gate_notice['message'] : '';
        $step = isset($this->setup_gate_notice['step']) ? sanitize_key((string) $this->setup_gate_notice['step']) : 'integrations';
        $setup_url = add_query_arg(
            array(
                'page' => 'aysa-setup-seo-profile',
                'step' => $step,
            ),
            admin_url('admin.php')
        );
        if ($message === '') {
            return;
        }
        echo '<div class="notice notice-' . esc_attr($type) . ' is-dismissible"><p><strong>' . esc_html__('AYSA Setup:', 'aysaaiseo') . '</strong> ' . esc_html($message) . ' <a href="' . esc_url($setup_url) . '">' . esc_html__('Open Integrations', 'aysaaiseo') . '</a>.</p></div>';
    }

    public function render_notifications_notice() {
        if (!$this->is_aysa_admin_request()) {
            return;
        }

        if (!$this->check_authentication(false)) {
            return;
        }

        $notice = $this->get_cached_notification_notice();
        if (!is_array($notice) || empty($notice['title'])) {
            return;
        }

        $type = isset($notice['severity']) ? sanitize_html_class((string) $notice['severity']) : 'info';
        $type_class = 'notice-info';
        if ($type === 'warning') {
            $type_class = 'notice-warning';
        } elseif ($type === 'error' || $type === 'critical') {
            $type_class = 'notice-error';
        } elseif ($type === 'success') {
            $type_class = 'notice-success';
        }

        $title = (string) $notice['title'];
        $body = (string) ($notice['body'] ?? '');
        $action_url = !empty($notice['action_url']) ? (string) $notice['action_url'] : admin_url('admin.php?page=aysa-settings&tab=notifications');
        ?>
        <div class="notice <?php echo esc_attr($type_class); ?>">
            <p><strong><?php echo esc_html($title); ?></strong></p>
            <?php if ($body !== '') : ?>
                <p><?php echo esc_html($body); ?></p>
            <?php endif; ?>
            <p><a class="button button-secondary" href="<?php echo esc_url($action_url); ?>">Open AYSA notifications</a></p>
        </div>
        <?php
    }

    private function get_cached_notification_notice() {
        $current_user_id = get_current_user_id();
        if ($current_user_id <= 0) {
            return null;
        }

        $cache_key = 'aysa_notification_notice_' . $current_user_id;
        $cached = get_transient($cache_key);
        if ($cached !== false) {
            return is_array($cached) ? $cached : null;
        }

        $result = $this->make_app_aysa_notifications_request('/bridge/plugin-notifications');
        $notice = null;
        if (!empty($result['ok']) && !empty($result['notifications']) && is_array($result['notifications'])) {
            foreach ($result['notifications'] as $notification) {
                if (!is_array($notification)) {
                    continue;
                }

                $type = isset($notification['type']) ? (string) $notification['type'] : '';
                if (!in_array($type, array('credits_low', 'subscription_expiring'), true)) {
                    continue;
                }

                $notice = array(
                    'title' => (string) ($notification['title'] ?? 'AYSA update'),
                    'body' => (string) ($notification['body'] ?? ''),
                    'severity' => (string) ($notification['severity'] ?? 'warning'),
                    'action_url' => (string) ($notification['action_url'] ?? ''),
                );
                break;
            }
        }

        set_transient($cache_key, $notice ?: array(), 120);

        return $notice;
    }

    private function clear_cached_notification_notice() {
        $current_user_id = get_current_user_id();
        if ($current_user_id > 0) {
            delete_transient('aysa_notification_notice_' . $current_user_id);
        }
    }

    public function enforce_access_key_for_aysa_ajax() {
        if (!wp_doing_ajax()) {
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed callback inventory: this read-only/core lifecycle request is authenticated by WordPress or an enclosing guard and performs no unapproved write.
        $action = isset($_REQUEST['action']) ? sanitize_key((string) wp_unslash($_REQUEST['action'])) : '';
        if ($action === '' || strpos($action, 'aysa_') !== 0) {
            return;
        }

        if ($action === 'aysa_agent8_conversation') {
            // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed callback inventory: this read-only/core lifecycle request is authenticated by WordPress or an enclosing guard and performs no unapproved write.
            $mode = isset($_REQUEST['mode']) ? sanitize_key((string) wp_unslash($_REQUEST['mode'])) : 'bootstrap';
            if (in_array($mode, array('bootstrap', 'message'), true)) {
                return;
            }
        }
        if (strpos($action, 'aysa_agent8v3_') === 0) {
            return;
        }
        if (in_array($action, array('aysa_conversation_async_start', 'aysa_conversation_async_status'), true)) {
            // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed callback inventory: this read-only/core lifecycle request is authenticated by WordPress or an enclosing guard and performs no unapproved write.
            $task = isset($_REQUEST['task']) ? sanitize_key((string) wp_unslash($_REQUEST['task'])) : '';
            if ($task === 'activation') {
                return;
            }
        }

        $allow = array(
            'aysa_access_key_get_test',
            'aysa_access_key_verify',
            'aysa_activation_conversation',
            'aysa_activation_reset_test',
            'aysa_apprentice_reset_test',
            'aysa_agent8_factory_reset_dry_run',
            'aysa_agent8_factory_reset_execute',
            'aysa_agent8_notifications_fetch',
            'aysa_keyword_research_projection_sync',
            'aysa_technical_site_audit_projection_sync',
            'aysa_technical_pagespeed_projection_sync',
            'aysa_technical_schema_projection_sync',
            'aysa_technical_redirects_projection_sync',
            'aysa_technical_sitemap_robots_projection_sync',
            'aysa_get_auth_status',
            'aysa_notifications_fetch',
            'aysa_login',
            'aysa_verify_secret_key',
            'aysa_get_secret_key',
            'aysa_logout',
        );
        if (in_array($action, $allow, true)) {
            return;
        }

        $validation = null;
        if (
            in_array($action, array('aysa_wallet_get_snapshot', 'aysa_wallet_create_order', 'aysa_wallet_get_orders'), true)
            && $this->aysa_is_workspace_activated_for_plugin_ajax($validation)
        ) {
            return;
        }

        $validation = null;
        if ($this->aysa_is_access_key_valid_globally($validation)) {
            return;
        }

        $msg = is_array($validation) && !empty($validation['message'])
            ? (string) $validation['message']
            : 'Access key is required.';
        wp_send_json_error(array(
            'message' => $msg,
            'code' => 'access_key_blocked',
            'validation' => $validation,
        ), 403);
    }

    private function aysa_is_workspace_activated_for_plugin_ajax(&$validation = null) {
        if (!$this->aysa_current_user_can_workspace()) {
            $validation = array(
                'valid' => false,
                'message' => 'Insufficient permissions.',
                'code' => 'workspace_capability_required',
                'source' => 'plugin_ajax_workspace_activation',
            );
            return false;
        }

        if ($this->aysa_is_access_key_valid_globally($validation)) {
            return true;
        }

        if ($this->aysa_is_workspace_activated_for_admin_menu($validation)) {
            if (is_array($validation)) {
                $validation['source'] = 'plugin_ajax_' . (string) ($validation['source'] ?? 'workspace_activation_sync');
            }
            return true;
        }

        return false;
    }

    public function display_plugin_admin_page() {
        
        if (!$this->check_authentication()) {
            $aysa_section_title = 'Research & Analysis';
            include AYSA_PLUGIN_DIR . 'admin/partials/aysa-login-required.php';
            return;
        }
        echo '<div class="aysa-page-container">';

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed callback inventory: this read-only/core lifecycle request is authenticated by WordPress or an enclosing guard and performs no unapproved write.
        $requested_tab = isset($_GET['tab']) ? sanitize_text_field(wp_unslash($_GET['tab'])) : '';
        $current_tab = $requested_tab !== '' ? $requested_tab : $this->aysa_get_last_research_tab();
        $current_tab = $this->aysa_normalize_research_tab($current_tab);
        $this->aysa_save_last_research_tab($current_tab);
        switch ($current_tab) {
            case 'keyword-discovery':
                include_once AYSA_PLUGIN_DIR . 'admin/partials/aysa-keyword-discovery.php';
                break;
            case 'competitor-gap':
                include_once AYSA_PLUGIN_DIR . 'admin/partials/aysa-competitor-gap.php';
                break;
            case 'competitor-identification':
                include_once AYSA_PLUGIN_DIR . 'admin/partials/aysa-competitor-identification.php';
                break;
            case 'competitor-backlinks':
                include_once AYSA_PLUGIN_DIR . 'admin/partials/aysa-competitor-backlinks.php';
                break;
            case 'missing-pages':
                include_once AYSA_PLUGIN_DIR . 'admin/partials/aysa-missing-pages-v2.php';
                break;
            case 'content-strategy':
                include_once AYSA_PLUGIN_DIR . 'admin/partials/aysa-content-strategy.php';
                break;
            case 'sitemap-competition':
                include_once AYSA_PLUGIN_DIR . 'admin/partials/aysa-sitemap-competition.php';
                break;
            case 'google-local-seo':
                // Tab removed
                include_once AYSA_PLUGIN_DIR . 'admin/partials/aysa-admin-display.php';
                break;
            default:
                include_once AYSA_PLUGIN_DIR . 'admin/partials/aysa-admin-display.php';
                break;
        }
        echo '</div>'; // Close the container
    }
    
    public function display_technical_optimization_page() {
        if (!$this->check_authentication()) {
            $aysa_section_title = 'Technical Optimization';
            include AYSA_PLUGIN_DIR . 'admin/partials/aysa-login-required.php';
            return;
        }
        include_once AYSA_PLUGIN_DIR . 'admin/partials/aysa-technical-optimization.php';
    }
    
    public function display_on_page_optimization_page() {
        if (!$this->check_authentication()) {
            $aysa_section_title = 'On-Page Optimization';
            include AYSA_PLUGIN_DIR . 'admin/partials/aysa-login-required.php';
            return;
        }

        $aysa_section_title = 'On-Page Optimization';
        $aysa_user_id = get_current_user_id();
        $aysa_user_credits = get_option('aysa_user_credits', 0);

        include_once AYSA_PLUGIN_DIR . 'admin/partials/aysa-on-page-optimization-v2.php';
    }    
    public function display_off_page_optimization_page() {
        if (!$this->check_authentication()) {
            $aysa_section_title = 'Off-Page Optimization';
            include AYSA_PLUGIN_DIR . 'admin/partials/aysa-login-required.php';
            return;
        }
        include_once AYSA_PLUGIN_DIR . 'admin/partials/aysa-off-page-optimization.php';
    }

    private function aysa_conversation_async_supported_tasks() {
        return array('setup', 'adjustments', 'activation', 'monitoring', 'agent8');
    }

    private function aysa_project_jobs_state_key($project_id) {
        return 'aysa_project_jobs_state_' . intval($project_id);
    }

    private function aysa_project_jobs_get_state($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array(
                'project_id' => 0,
                'active_job_ids' => array(),
                'queue' => array(),
                'jobs' => array(),
                'recent' => array(),
                'updated_at' => 0,
            );
        }

        $raw = get_option($this->aysa_project_jobs_state_key($project_id), array());
        $state = is_array($raw) ? $raw : array();
        $state['project_id'] = $project_id;
        $state['active_job_ids'] = isset($state['active_job_ids']) && is_array($state['active_job_ids']) ? array_values($state['active_job_ids']) : array();
        $state['queue'] = isset($state['queue']) && is_array($state['queue']) ? array_values($state['queue']) : array();
        $state['jobs'] = isset($state['jobs']) && is_array($state['jobs']) ? $state['jobs'] : array();
        $state['recent'] = isset($state['recent']) && is_array($state['recent']) ? array_values($state['recent']) : array();
        $state['updated_at'] = intval($state['updated_at'] ?? 0);

        foreach ($state['active_job_ids'] as $idx => $job_id) {
            if (!isset($state['jobs'][$job_id]) || !is_array($state['jobs'][$job_id])) {
                unset($state['active_job_ids'][$idx]);
                continue;
            }
            $job_status = (string) ($state['jobs'][$job_id]['status'] ?? '');
            if (!in_array($job_status, array('running', 'delegated'), true)) {
                unset($state['active_job_ids'][$idx]);
            }
        }
        $state['active_job_ids'] = array_values(array_unique($state['active_job_ids']));

        foreach ($state['queue'] as $idx => $job_id) {
            if (!isset($state['jobs'][$job_id]) || !is_array($state['jobs'][$job_id])) {
                unset($state['queue'][$idx]);
                continue;
            }
            $job_status = (string) ($state['jobs'][$job_id]['status'] ?? '');
            if ($job_status !== 'queued') {
                unset($state['queue'][$idx]);
            }
        }
        $state['queue'] = array_values(array_unique($state['queue']));

        foreach ($state['recent'] as $idx => $job_id) {
            if (!isset($state['jobs'][$job_id]) || !is_array($state['jobs'][$job_id])) {
                unset($state['recent'][$idx]);
            }
        }
        $state['recent'] = array_values(array_unique($state['recent']));

        return $state;
    }

    private function aysa_project_jobs_save_state($project_id, $state) {
        $project_id = intval($project_id);
        if ($project_id <= 0 || !is_array($state)) {
            return;
        }
        $state['project_id'] = $project_id;
        $state['updated_at'] = time();
        update_option($this->aysa_project_jobs_state_key($project_id), $state, false);
    }

    private function aysa_project_jobs_resource_groups_intersect($left, $right) {
        $left = array_values(array_unique(array_filter(array_map('strval', is_array($left) ? $left : array()))));
        $right = array_values(array_unique(array_filter(array_map('strval', is_array($right) ? $right : array()))));
        if ($left === array() || $right === array()) {
            return false;
        }
        return array_values(array_intersect($left, $right)) !== array();
    }

    private function aysa_project_jobs_trim_recent($recent, $limit = 10) {
        $recent = array_values(array_unique(array_filter(is_array($recent) ? $recent : array(), function($job_id) {
            return is_string($job_id) && $job_id !== '';
        })));
        if (count($recent) > $limit) {
            $recent = array_slice($recent, 0, $limit);
        }
        return $recent;
    }

    private function aysa_project_jobs_metadata($module, $job_type, $overrides = array()) {
        $module = sanitize_key((string) $module);
        $job_type = sanitize_key((string) $job_type);
        $catalog = array(
            'research' => array(
                'keyword_discovery_refresh' => array(
                    'resource_groups' => array('research_context', 'gsc_fetch'),
                    'execution_family' => 'keyword_discovery',
                    'priority' => 60,
                    'billing_owner' => 'existing_executor',
                ),
                'competitor_gap' => array(
                    'resource_groups' => array('research_context', 'competitor_data'),
                    'execution_family' => 'research_async',
                    'priority' => 50,
                    'billing_owner' => 'existing_executor',
                ),
                'competitor_identification' => array(
                    'resource_groups' => array('research_context', 'competitor_data'),
                    'execution_family' => 'research_async',
                    'priority' => 45,
                    'billing_owner' => 'existing_executor',
                ),
                'keyword_strategy_generate' => array(
                    'resource_groups' => array('research_context', 'content_generation'),
                    'execution_family' => 'research_async',
                    'priority' => 55,
                    'billing_owner' => 'existing_executor',
                ),
                'sitemap_competition_scan' => array(
                    'resource_groups' => array('research_context', 'site_crawl', 'competitor_data'),
                    'execution_family' => 'sitemap_competition',
                    'priority' => 40,
                    'billing_owner' => 'existing_executor',
                ),
                'content_strategy_generation' => array(
                    'resource_groups' => array('research_context', 'content_generation'),
                    'execution_family' => 'content_strategy',
                    'priority' => 35,
                    'billing_owner' => 'existing_executor',
                ),
            ),
            'monitoring' => array(
                'rank_refresh' => array(
                    'resource_groups' => array('monitoring_refresh'),
                    'execution_family' => 'monitoring_rank_refresh',
                    'priority' => 30,
                    'billing_owner' => 'existing_executor',
                ),
                'ai_refresh' => array(
                    'resource_groups' => array('monitoring_refresh'),
                    'execution_family' => 'monitoring_ai_async',
                    'priority' => 30,
                    'billing_owner' => 'existing_executor',
                ),
                'keywords_overview_refresh' => array(
                    'resource_groups' => array('monitoring_refresh'),
                    'execution_family' => 'monitoring_keywords_async',
                    'priority' => 30,
                    'billing_owner' => 'existing_executor',
                ),
            ),
            'technical' => array(
                'site_audit' => array(
                    'resource_groups' => array('site_crawl', 'technical_snapshot'),
                    'execution_family' => 'technical_site_audit',
                    'priority' => 25,
                    'billing_owner' => 'existing_executor',
                ),
                'pagespeed_report' => array(
                    'resource_groups' => array('technical_snapshot'),
                    'execution_family' => 'pagespeed_report',
                    'priority' => 25,
                    'billing_owner' => 'existing_executor',
                ),
                'schema_job' => array(
                    'resource_groups' => array('technical_snapshot'),
                    'execution_family' => 'technical_schema',
                    'priority' => 25,
                    'billing_owner' => 'existing_executor',
                ),
            ),
            'onpage' => array(
                'meta_generation' => array(
                    'resource_groups' => array('onpage_write', 'content_generation'),
                    'execution_family' => 'metabox_async',
                    'priority' => 25,
                    'billing_owner' => 'existing_executor',
                ),
                'rewrite_generation' => array(
                    'resource_groups' => array('onpage_write', 'content_generation'),
                    'execution_family' => 'metabox_async',
                    'priority' => 25,
                    'billing_owner' => 'existing_executor',
                ),
                'page_update_apply' => array(
                    'resource_groups' => array('onpage_write'),
                    'execution_family' => 'onpage_write',
                    'priority' => 25,
                    'billing_owner' => 'existing_executor',
                ),
            ),
            'offpage' => array(
                'gbp_refresh' => array(
                    'resource_groups' => array('offpage_gbp'),
                    'execution_family' => 'gbp_jobs',
                    'priority' => 25,
                    'billing_owner' => 'existing_executor',
                ),
                'exchange_action' => array(
                    'resource_groups' => array('offpage_exchange'),
                    'execution_family' => 'offpage_async',
                    'priority' => 25,
                    'billing_owner' => 'existing_executor',
                ),
                'draft_approve' => array(
                    'resource_groups' => array('offpage_exchange'),
                    'execution_family' => 'offpage_async',
                    'priority' => 25,
                    'billing_owner' => 'existing_executor',
                ),
            ),
            'conversation' => array(
                'conversation_async' => array(
                    'resource_groups' => array('conversation_state'),
                    'execution_family' => 'conversation_async',
                    'priority' => 20,
                    'billing_owner' => 'existing_executor',
                ),
            ),
        );
        $base = $catalog[$module][$job_type] ?? array(
            'resource_groups' => array($module !== '' ? $module : 'generic'),
            'execution_family' => 'custom',
            'priority' => 10,
            'billing_owner' => 'existing_executor',
        );
        if (is_array($overrides) && $overrides !== array()) {
            $base = array_merge($base, $overrides);
        }
        $base['resource_groups'] = array_values(array_unique(array_filter(array_map('sanitize_key', (array) ($base['resource_groups'] ?? array())))));
        $base['execution_family'] = sanitize_key((string) ($base['execution_family'] ?? 'custom'));
        $base['priority'] = intval($base['priority'] ?? 10);
        $base['billing_owner'] = sanitize_key((string) ($base['billing_owner'] ?? 'existing_executor'));
        return $base;
    }

    private function aysa_project_jobs_make_worker_ref($execution_family, $job_type, $run_id) {
        $execution_family = sanitize_key((string) $execution_family);
        $job_type = sanitize_key((string) $job_type);
        $run_id = trim((string) $run_id);
        if ($execution_family === '' || $job_type === '' || $run_id === '') {
            return '';
        }
        return $execution_family . ':' . $job_type . ':' . $run_id;
    }

    private function aysa_project_jobs_build_envelope($project_id, $module, $job_type, array $metadata = array(), array $payload = array()) {
        $project_id = intval($project_id);
        $module = sanitize_key((string) $module);
        $job_type = sanitize_key((string) $job_type);
        $resolved = $this->aysa_project_jobs_metadata($module, $job_type, $metadata);
        $dedupe_key = trim((string) ($metadata['dedupe_key'] ?? ''));
        if ($dedupe_key === '') {
            $dedupe_key = md5(wp_json_encode(array(
                'module' => $module,
                'job_type' => $job_type,
                'payload' => $payload,
            )));
        }
        return array(
            'job_id' => uniqid('pjob_', true),
            'project_id' => $project_id,
            'module' => $module,
            'job_type' => $job_type,
            'resource_groups' => $resolved['resource_groups'],
            'execution_family' => (string) $resolved['execution_family'],
            'status' => 'queued',
            'priority' => intval($resolved['priority']),
            'dedupe_key' => $dedupe_key,
            'depends_on_job_ids' => array_values(array_unique(array_filter((array) ($metadata['depends_on_job_ids'] ?? array())))),
            'billing_owner' => (string) $resolved['billing_owner'],
            'input_summary' => isset($metadata['input_summary']) && is_array($metadata['input_summary']) ? $metadata['input_summary'] : array(),
            'worker_ref' => '',
            'worker_status' => '',
            'result_summary' => null,
            'result_payload' => null,
            'error_summary' => '',
            'blocked_by' => array(),
            'executor_payload' => $payload,
            'created_at' => time(),
            'started_at' => 0,
            'updated_at' => time(),
            'finished_at' => 0,
        );
    }

    private function aysa_project_jobs_find_duplicate_job(array $state, $module, $job_type, $dedupe_key) {
        foreach ((array) ($state['jobs'] ?? array()) as $job_id => $job) {
            if (!is_array($job)) {
                continue;
            }
            if ((string) ($job['module'] ?? '') !== (string) $module || (string) ($job['job_type'] ?? '') !== (string) $job_type) {
                continue;
            }
            if ((string) ($job['dedupe_key'] ?? '') !== (string) $dedupe_key) {
                continue;
            }
            $status = (string) ($job['status'] ?? '');
            if (in_array($status, array('queued', 'running', 'delegated'), true)) {
                return $job;
            }
        }
        return null;
    }

    private function aysa_project_jobs_get_blocking_job_ids(array $state, array $job) {
        $blocking = array();
        foreach ((array) ($state['active_job_ids'] ?? array()) as $active_job_id) {
            $active = $state['jobs'][$active_job_id] ?? null;
            if (!is_array($active)) {
                continue;
            }
            if (!$this->aysa_project_jobs_resource_groups_intersect($job['resource_groups'] ?? array(), $active['resource_groups'] ?? array())) {
                continue;
            }
            $blocking[] = (string) $active_job_id;
        }
        return array_values(array_unique($blocking));
    }

    private function aysa_project_jobs_start_enqueued_job(&$state, &$job) {
        $start = $this->aysa_project_jobs_delegate_start($job);
        if (is_wp_error($start)) {
            $job['status'] = 'failed';
            $job['error_summary'] = $start->get_error_message();
            $job['updated_at'] = time();
            $job['finished_at'] = time();
            return $job;
        }

        $job['started_at'] = intval($start['started_at'] ?? time());
        $job['updated_at'] = time();
        $job['worker_ref'] = (string) ($start['worker_ref'] ?? '');
        $job['worker_status'] = (string) ($start['worker_status'] ?? '');
        $job['result_summary'] = $start['result_summary'] ?? null;
        $job['result_payload'] = $start['result'] ?? null;
        $job['error_summary'] = (string) ($start['error_summary'] ?? '');
        $job['blocked_by'] = array();

        $resolved_status = sanitize_key((string) ($start['status'] ?? 'running'));
        if ($resolved_status === 'completed') {
            $job['status'] = 'completed';
            $job['finished_at'] = time();
            $state['recent'] = $this->aysa_project_jobs_trim_recent(array_merge(array($job['job_id']), (array) ($state['recent'] ?? array())));
        } elseif ($resolved_status === 'failed') {
            $job['status'] = 'failed';
            $job['finished_at'] = time();
            $state['recent'] = $this->aysa_project_jobs_trim_recent(array_merge(array($job['job_id']), (array) ($state['recent'] ?? array())));
        } else {
            $job['status'] = 'running';
            if (!in_array($job['job_id'], $state['active_job_ids'], true)) {
                $state['active_job_ids'][] = $job['job_id'];
            }
        }

        return $job;
    }

    private function aysa_project_jobs_delegate_start(array $job) {
        $project_id = intval($job['project_id'] ?? 0);
        $job_type = (string) ($job['job_type'] ?? '');
        $execution_family = (string) ($job['execution_family'] ?? '');
        $payload = isset($job['executor_payload']) && is_array($job['executor_payload']) ? $job['executor_payload'] : array();

        if ($project_id <= 0 || $job_type === '' || $execution_family === '') {
            return new WP_Error('invalid_project_job', 'Invalid project job envelope.');
        }

        switch ($execution_family) {
            case 'keyword_discovery':
                $apply_to_wp = !empty($payload['apply_to_wp']);
                $refresh_kind = (string) ($payload['refresh_kind'] ?? 'gsc_auto');
                $start = $this->aysa_keyword_discovery_start_background_refresh($project_id, $apply_to_wp, $refresh_kind);
                if (is_wp_error($start)) {
                    return $start;
                }
                $status = isset($start['status']) && is_array($start['status']) ? $start['status'] : array();
                $run_id = (string) ($status['run_id'] ?? '');
                return array(
                    'status' => in_array((string) ($status['status'] ?? ''), array('running', 'queued'), true) ? 'running' : ((string) ($status['status'] ?? '') ?: 'running'),
                    'worker_ref' => $this->aysa_project_jobs_make_worker_ref($execution_family, $job_type, $run_id),
                    'worker_status' => (string) ($status['status'] ?? ''),
                    'result_summary' => array(
                        'already_running' => !empty($start['already_running']),
                        'pending_item' => isset($start['pending_item']) && is_array($start['pending_item']) ? $start['pending_item'] : array(),
                    ),
                    'started_at' => intval($status['started_at'] ?? time()),
                );
            case 'research_async':
                $task = (string) ($payload['task'] ?? $job_type);
                $request = isset($payload['request']) && is_array($payload['request']) ? $payload['request'] : array();
                return $this->aysa_research_async_schedule_request($task, $request);
            case 'sitemap_competition':
                $start = $this->aysa_sitemap_competition_start_queued_scan($payload);
                if (is_wp_error($start)) {
                    return $start;
                }
                return array(
                    'status' => 'running',
                    'worker_ref' => $this->aysa_project_jobs_make_worker_ref($execution_family, $job_type, (string) intval($start['local_run_id'] ?? 0)),
                    'worker_status' => 'queued',
                    'result_summary' => array(
                        'local_run_id' => intval($start['local_run_id'] ?? 0),
                        'queued' => !empty($start['queued']),
                    ),
                    'started_at' => time(),
                );
            case 'monitoring_keywords_async':
                $request = isset($payload['request']) && is_array($payload['request']) ? $payload['request'] : array();
                return $this->aysa_monitoring_keywords_async_schedule_request($request);
            case 'monitoring_rank_refresh':
                return $this->aysa_monitoring_rank_refresh_start_queued($payload);
            case 'monitoring_ai_async':
                return $this->aysa_monitoring_ai_refresh_start_queued($payload);
            case 'technical_site_audit':
                return $this->aysa_technical_site_audit_start_queued($payload);
            case 'pagespeed_report':
                return $this->aysa_pagespeed_report_start_queued($payload);
            case 'technical_schema':
                return $this->aysa_technical_schema_start_queued_job($payload);
            default:
                return new WP_Error('unsupported_execution_family', 'Unsupported execution family `' . $execution_family . '`.');
        }
    }

    private function aysa_project_jobs_dispatch_next_eligible(&$state) {
        if (!is_array($state) || empty($state['queue']) || empty($state['jobs'])) {
            return;
        }

        $new_queue = array();
        foreach ((array) $state['queue'] as $job_id) {
            $job = $state['jobs'][$job_id] ?? null;
            if (!is_array($job) || (string) ($job['status'] ?? '') !== 'queued') {
                continue;
            }

            $depends_on = array_values(array_filter((array) ($job['depends_on_job_ids'] ?? array())));
            $dependency_block = false;
            foreach ($depends_on as $dep_job_id) {
                $dep = $state['jobs'][$dep_job_id] ?? null;
                if (!is_array($dep) || !in_array((string) ($dep['status'] ?? ''), array('completed'), true)) {
                    $dependency_block = true;
                    break;
                }
            }
            if ($dependency_block) {
                $new_queue[] = $job_id;
                continue;
            }

            $blocking = $this->aysa_project_jobs_get_blocking_job_ids($state, $job);
            if ($blocking !== array()) {
                $job['blocked_by'] = $blocking;
                $job['updated_at'] = time();
                $state['jobs'][$job_id] = $job;
                $new_queue[] = $job_id;
                continue;
            }

            $job['blocked_by'] = array();
            $this->aysa_project_jobs_start_enqueued_job($state, $job);
            $state['jobs'][$job_id] = $job;
        }

        $state['queue'] = array_values($new_queue);
    }

    private function aysa_project_jobs_finalize($project_id, $worker_ref, $status, $result_summary = null, $error_summary = '') {
        $project_id = intval($project_id);
        $worker_ref = trim((string) $worker_ref);
        $status = sanitize_key((string) $status);
        if ($project_id <= 0 || $worker_ref === '' || !in_array($status, array('completed', 'failed'), true)) {
            return;
        }

        $state = $this->aysa_project_jobs_get_state($project_id);
        if (empty($state['jobs'])) {
            return;
        }

        $target_job_id = '';
        foreach ((array) $state['jobs'] as $job_id => $job) {
            if (!is_array($job)) {
                continue;
            }
            if ((string) ($job['worker_ref'] ?? '') === $worker_ref && in_array((string) ($job['status'] ?? ''), array('running', 'delegated', 'queued'), true)) {
                $target_job_id = (string) $job_id;
                break;
            }
        }
        if ($target_job_id === '') {
            return;
        }

        $job = $state['jobs'][$target_job_id];
        $job['status'] = $status;
        $job['worker_status'] = $status;
        $job['result_summary'] = $result_summary;
        $job['error_summary'] = $status === 'failed' ? (string) $error_summary : '';
        $job['updated_at'] = time();
        $job['finished_at'] = time();
        $state['jobs'][$target_job_id] = $job;

        $state['active_job_ids'] = array_values(array_filter((array) ($state['active_job_ids'] ?? array()), function($job_id) use ($target_job_id) {
            return (string) $job_id !== (string) $target_job_id;
        }));
        $state['recent'] = $this->aysa_project_jobs_trim_recent(array_merge(array($target_job_id), (array) ($state['recent'] ?? array())));
        $this->aysa_project_jobs_dispatch_next_eligible($state);
        $this->aysa_project_jobs_save_state($project_id, $state);
    }

    private function aysa_project_jobs_to_rack(array $state) {
        $jobs = isset($state['jobs']) && is_array($state['jobs']) ? $state['jobs'] : array();
        $summarize = function($job) {
            if (!is_array($job)) {
                return array();
            }
            return array(
                'job_id' => (string) ($job['job_id'] ?? ''),
                'module' => (string) ($job['module'] ?? ''),
                'job_type' => (string) ($job['job_type'] ?? ''),
                'status' => (string) ($job['status'] ?? ''),
                'worker_ref' => (string) ($job['worker_ref'] ?? ''),
                'worker_status' => (string) ($job['worker_status'] ?? ''),
                'resource_groups' => array_values((array) ($job['resource_groups'] ?? array())),
                'blocked_by' => array_values((array) ($job['blocked_by'] ?? array())),
                'created_at' => intval($job['created_at'] ?? 0),
                'started_at' => intval($job['started_at'] ?? 0),
                'updated_at' => intval($job['updated_at'] ?? 0),
                'finished_at' => intval($job['finished_at'] ?? 0),
            );
        };

        $running = array();
        foreach ((array) ($state['active_job_ids'] ?? array()) as $job_id) {
            if (isset($jobs[$job_id]) && is_array($jobs[$job_id])) {
                $running[] = $summarize($jobs[$job_id]);
            }
        }

        $queued = array();
        foreach ((array) ($state['queue'] ?? array()) as $job_id) {
            if (isset($jobs[$job_id]) && is_array($jobs[$job_id])) {
                $queued[] = $summarize($jobs[$job_id]);
            }
        }

        $completed = array();
        $failed = array();
        foreach ((array) ($state['recent'] ?? array()) as $job_id) {
            if (!isset($jobs[$job_id]) || !is_array($jobs[$job_id])) {
                continue;
            }
            $row = $summarize($jobs[$job_id]);
            if (($row['status'] ?? '') === 'completed') {
                $completed[] = $row;
            } elseif (($row['status'] ?? '') === 'failed') {
                $failed[] = $row;
            }
        }

        return array(
            'project_id' => intval($state['project_id'] ?? 0),
            'running_now' => $running,
            'queued_jobs' => $queued,
            'queued_next' => $queued !== array() ? $queued[0] : array(),
            'recently_completed' => $completed,
            'recently_failed' => $failed,
            'updated_at' => intval($state['updated_at'] ?? 0),
        );
    }

    private function aysa_project_jobs_find_latest_job($project_id, $module, $job_type, $statuses = array()) {
        $state = $this->aysa_project_jobs_get_state($project_id);
        $jobs = isset($state['jobs']) && is_array($state['jobs']) ? $state['jobs'] : array();
        $statuses = array_values(array_filter(array_map('strval', is_array($statuses) ? $statuses : array())));
        $best = null;
        foreach ($jobs as $job) {
            if (!is_array($job)) {
                continue;
            }
            if ((string) ($job['module'] ?? '') !== (string) $module || (string) ($job['job_type'] ?? '') !== (string) $job_type) {
                continue;
            }
            if ($statuses !== array() && !in_array((string) ($job['status'] ?? ''), $statuses, true)) {
                continue;
            }
            if ($best === null || intval($job['updated_at'] ?? 0) > intval($best['updated_at'] ?? 0)) {
                $best = $job;
            }
        }
        return is_array($best) ? $best : null;
    }

    private function aysa_project_jobs_enqueue($project_id, $module, $job_type, array $payload = array(), array $metadata = array()) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return new WP_Error('invalid_project_id', 'Invalid project id.');
        }

        $state = $this->aysa_project_jobs_get_state($project_id);
        $job = $this->aysa_project_jobs_build_envelope($project_id, $module, $job_type, $metadata, $payload);
        $duplicate = $this->aysa_project_jobs_find_duplicate_job($state, $job['module'], $job['job_type'], $job['dedupe_key']);
        if (is_array($duplicate)) {
            return array(
                'job' => $duplicate,
                'deduped' => true,
                'rack' => $this->aysa_project_jobs_to_rack($state),
            );
        }

        $blocking = $this->aysa_project_jobs_get_blocking_job_ids($state, $job);
        if ($blocking !== array()) {
            $job['blocked_by'] = $blocking;
            $state['jobs'][$job['job_id']] = $job;
            $state['queue'][] = $job['job_id'];
            $state['queue'] = array_values(array_unique($state['queue']));
            $this->aysa_project_jobs_save_state($project_id, $state);
            return array(
                'job' => $job,
                'deduped' => false,
                'queued' => true,
                'rack' => $this->aysa_project_jobs_to_rack($state),
            );
        }

        $state['jobs'][$job['job_id']] = $job;
        $this->aysa_project_jobs_start_enqueued_job($state, $job);
        $state['jobs'][$job['job_id']] = $job;
        $this->aysa_project_jobs_save_state($project_id, $state);

        return array(
            'job' => $job,
            'deduped' => false,
            'queued' => false,
            'rack' => $this->aysa_project_jobs_to_rack($state),
        );
    }

    public function handle_project_jobs_status() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }
        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        $state = $this->aysa_project_jobs_get_state($project_id);
        wp_send_json_success($this->aysa_project_jobs_to_rack($state));
    }

    private function aysa_conversation_async_status_key($task, $project_id, $wp_user_id, $site_host) {
        $host_hash = md5(strtolower(trim((string) $site_host)));
        return 'aysa_conversation_async_' . sanitize_key((string) $task) . '_' . intval($project_id) . '_' . intval($wp_user_id) . '_' . $host_hash;
    }

    private function aysa_conversation_async_get_status($task, $project_id, $wp_user_id, $site_host) {
        $raw = get_option($this->aysa_conversation_async_status_key($task, $project_id, $wp_user_id, $site_host), array());
        return is_array($raw) ? $raw : array();
    }

    private function aysa_conversation_async_save_status($task, $project_id, $wp_user_id, $site_host, $status) {
        update_option(
            $this->aysa_conversation_async_status_key($task, $project_id, $wp_user_id, $site_host),
            is_array($status) ? $status : array(),
            false
        );
    }

    private function aysa_conversation_async_output_status($status) {
        if (!is_array($status)) {
            return array(
                'status' => 'idle',
                'result' => null,
                'error' => '',
            );
        }
        $clean = $status;
        unset($clean['_request']);
        return $this->aysa_agent8_bridge_compact_payload_if_needed($clean, array(
            'task' => (string) ($clean['task'] ?? ''),
            'run_id' => (string) ($clean['run_id'] ?? ''),
            'project_id' => intval($clean['project_id'] ?? 0),
            'conversation_id' => '',
            'source' => 'async_status_output',
        ));
    }

    private function aysa_agent8_bridge_log($message, $context = array()) {
        $safe_context = array();
        if (is_array($context)) {
            foreach (array('task', 'run_id', 'project_id', 'conversation_id', 'source', 'error') as $key) {
                if (isset($context[$key]) && $context[$key] !== '') {
                    $safe_context[$key] = is_scalar($context[$key]) ? (string) $context[$key] : wp_json_encode($context[$key]);
                }
            }
        }
        $suffix = !empty($safe_context) ? ' ' . wp_json_encode($safe_context) : '';
        if (function_exists('aysa_debug_log')) {
            aysa_debug_log('[AYSA AGENT8 BRIDGE] ' . (string) $message . $suffix);
        }
    }

    private function aysa_agent8_bridge_is_post_research_response($response_result) {
        if (!is_array($response_result)) {
            return false;
        }
        $phase = sanitize_key((string) ($response_result['phase'] ?? ''));
        $current_step = sanitize_key((string) ($response_result['current_step'] ?? ''));
        $module = sanitize_key((string) ($response_result['module'] ?? ''));
        $tab = sanitize_key((string) ($response_result['tab'] ?? ''));
        $onboarding = isset($response_result['state']['context']['onboarding']) && is_array($response_result['state']['context']['onboarding'])
            ? $response_result['state']['context']['onboarding']
            : array();
        $onboarding_phase = sanitize_key((string) ($onboarding['phase_key'] ?? ''));
        $onboarding_module = sanitize_key((string) ($onboarding['module'] ?? ''));
        $onboarding_tab = sanitize_key((string) ($onboarding['tab'] ?? ''));

        return $phase === 'post_research_workspace'
            || $current_step === 'post_research_workspace'
            || $onboarding_phase === 'post_research_workspace'
            || ($module === 'technical_seo' && $tab === 'site_audit')
            || ($onboarding_module === 'technical_seo' && $onboarding_tab === 'site_audit');
    }

    private function aysa_agent8_bridge_post_research_actions() {
        return array(
            array('type' => 'action', 'action_key' => 'show_missing_pages', 'label' => 'Open Missing Pages'),
            array('type' => 'action', 'action_key' => 'show_keyword_mapping', 'label' => 'Open Keyword Mapping'),
            array('type' => 'action', 'action_key' => 'show_planned_content', 'label' => 'Open Content Plan'),
            array('type' => 'action', 'action_key' => 'show_found_keywords', 'label' => 'Review Keywords'),
            array('type' => 'action', 'action_key' => 'show_research_competitors', 'label' => 'Refine Competitors'),
            array('type' => 'action', 'action_key' => 'refresh_technical_optimization', 'label' => 'Start Technical Audit'),
            array('type' => 'action', 'action_key' => 'show_technical_overview', 'label' => 'View technical overview'),
            array('type' => 'action', 'action_key' => 'show_technical_site_audit', 'label' => 'Open Site Audit'),
        );
    }

    private function aysa_agent8_bridge_post_research_revisit_actions() {
        return array(
            array('type' => 'action', 'action_key' => 'show_found_keywords', 'label' => 'Review Keywords'),
            array('type' => 'action', 'action_key' => 'show_research_competitors', 'label' => 'Review Competitors'),
            array('type' => 'action', 'action_key' => 'show_keyword_mapping', 'label' => 'Review Keyword Mapping'),
            array('type' => 'action', 'action_key' => 'show_missing_pages', 'label' => 'Review Missing Pages'),
            array('type' => 'action', 'action_key' => 'show_planned_content', 'label' => 'Review Content Plan'),
            array('type' => 'action', 'action_key' => 'show_publishing_calendar', 'label' => 'Publishing Calendar'),
        );
    }

    private function aysa_agent8_bridge_primary_guidance_actions($response_result) {
        if (!is_array($response_result)) {
            return array();
        }
        $action_result = isset($response_result['action_result']) && is_array($response_result['action_result'])
            ? $response_result['action_result']
            : array();
        $projection = isset($action_result['projection']) && is_array($action_result['projection'])
            ? $action_result['projection']
            : array();
        $type = sanitize_key((string) ($projection['type'] ?? ''));

        if ($type === 'technical_overview' || $type === 'technical_site_audit') {
            return array(
                array('type' => 'action', 'action_key' => 'show_onpage_optimization', 'label' => 'Open On-Page SEO', 'style' => 'primary'),
            );
        }

        if ($type !== 'onpage_optimization') {
            return array();
        }

        $summary = isset($action_result['summary']) && is_array($action_result['summary'])
            ? $action_result['summary']
            : array();
        $next_action = '';
        $label = '';
        if ((int) ($summary['keyword_mapping_needed'] ?? 0) > 0) {
            $next_action = 'show_onpage_keyword_association';
            $label = 'Review pages without keyword mapping';
        } elseif ((int) ($summary['keyword_validation_ready'] ?? 0) > 0) {
            $next_action = 'show_onpage_keyword_validation';
            $label = 'Validate page keywords';
        } elseif ((int) ($summary['meta_ready'] ?? 0) > 0) {
            $next_action = 'show_onpage_meta_generation';
            $label = 'Review missing or weak metadata';
        } elseif ((int) ($summary['rewrite_recommended'] ?? 0) > 0 || (int) ($summary['rewrite_review'] ?? 0) > 0) {
            $next_action = 'show_onpage_rewrite_generation';
            $label = 'Review pages that may need rewrites';
        } elseif ((int) ($summary['apply_queue'] ?? 0) > 0) {
            $next_action = 'show_onpage_apply_queue';
            $label = 'Review the apply queue';
        }

        return $next_action !== '' ? array(
            array('type' => 'action', 'action_key' => $next_action, 'label' => $label, 'style' => 'primary'),
        ) : array();
    }

    private function aysa_agent8_bridge_compact_post_research_response($response_result) {
        if (!is_array($response_result)) {
            return $response_result;
        }

        $messages = isset($response_result['messages']) && is_array($response_result['messages'])
            ? array_slice($response_result['messages'], -3)
            : array();
        $assistant_message = trim((string) ($response_result['assistant_message'] ?? ''));
        if ($assistant_message === '') {
            $assistant_message = 'Research is complete. Start by reviewing Missing Pages, then Keyword Mapping, then Content Plan. Technical SEO is available next.';
        }

        $state = isset($response_result['state']) && is_array($response_result['state']) ? $response_result['state'] : array();
        $meta = isset($state['meta']) && is_array($state['meta']) ? $state['meta'] : array();
        $context = isset($state['context']) && is_array($state['context']) ? $state['context'] : array();
        $onboarding = isset($context['onboarding']) && is_array($context['onboarding']) ? $context['onboarding'] : array();
        $onboarding['phase_key'] = 'post_research_workspace';
        $onboarding['module'] = 'technical_seo';
        $onboarding['tab'] = 'site_audit';
        $onboarding['next_step'] = 'review_or_execute';

        $compact_context = array(
            'onboarding' => $onboarding,
        );
        if (isset($context['post_research_artifact']) && is_array($context['post_research_artifact'])) {
            $compact_context['post_research_artifact'] = $this->aysa_agent8_transport_reduce_value($context['post_research_artifact'], 0, 12);
        }
        if (isset($context['latest_keyword_research_run']) && is_array($context['latest_keyword_research_run'])) {
            $compact_context['latest_keyword_research_run'] = $this->aysa_agent8_transport_reduce_value($context['latest_keyword_research_run'], 0, 12);
        }
        if (isset($context['research_review']) && is_array($context['research_review'])) {
            $compact_context['research_review'] = $context['research_review'];
        }

        $raw_action_result = isset($response_result['action_result']) && is_array($response_result['action_result'])
            ? $response_result['action_result']
            : array();
        $raw_ui_command = isset($raw_action_result['ui_command']) && is_array($raw_action_result['ui_command'])
            ? $raw_action_result['ui_command']
            : array();
        $is_found_keywords_modal = (($raw_action_result['action_key'] ?? '') === 'show_found_keywords')
            || (
                ($raw_ui_command['type'] ?? '') === 'open_keyword_review'
                && ($raw_ui_command['review_type'] ?? '') === 'found_keywords'
                && isset($raw_ui_command['pagination'])
            );
        $is_keyword_mapping_modal = (($raw_action_result['action_key'] ?? '') === 'show_keyword_mapping')
            || (
                ($raw_ui_command['type'] ?? '') === 'open_keyword_review'
                && ($raw_ui_command['review_type'] ?? '') === 'keyword_mapping'
                && isset($raw_ui_command['pagination'])
            );
        $is_missing_pages_modal = (($raw_action_result['action_key'] ?? '') === 'show_missing_pages')
            || (
                ($raw_ui_command['type'] ?? '') === 'open_missing_pages_review'
                && isset($raw_ui_command['pagination'])
            );
        $is_planned_content_modal = (($raw_action_result['action_key'] ?? '') === 'show_planned_content')
            || (
                ($raw_ui_command['type'] ?? '') === 'open_content_strategy_review'
                && ($raw_ui_command['review_type'] ?? '') === 'planned_content'
                && isset($raw_ui_command['pagination'])
            );
        $is_site_audit_review = (($raw_action_result['action_key'] ?? '') === 'show_technical_site_audit')
            || (($raw_ui_command['type'] ?? '') === 'open_technical_site_audit_review');
        $is_site_audit_bundle_review = in_array(($raw_action_result['action_key'] ?? ''), array(
            'prepare_metadata_remediation',
            'prepare_response_code_review',
            'prepare_indexability_review',
            'prepare_internal_linking_plan',
            'prepare_image_alt_text',
            'apply_metadata_remediation',
            'verify_metadata_remediation',
            'apply_response_code_review',
            'verify_response_code_review',
            'apply_indexability_review',
            'verify_indexability_review',
            'apply_internal_linking_plan',
            'verify_internal_linking_plan',
            'apply_image_alt_text',
            'verify_image_alt_text',
            'ignore_site_audit_action_bundle',
        ), true)
            || (($raw_ui_command['type'] ?? '') === 'open_technical_site_audit_bundle_review');
        $action_result = !empty($raw_action_result)
            ? (($is_site_audit_review || $is_site_audit_bundle_review) ? $this->aysa_agent8_transport_reduce_site_audit_action_result($raw_action_result) : (($is_found_keywords_modal || $is_keyword_mapping_modal || $is_missing_pages_modal || $is_planned_content_modal) ? $raw_action_result : $this->aysa_agent8_transport_reduce_value($raw_action_result, 0, 12)))
            : array('ok' => true, 'projection' => array('type' => 'post_research_workspace'));

        $response_result['state'] = array(
            'meta' => $meta,
            'context' => $compact_context,
        );
        $response_result['phase'] = 'post_research_workspace';
        $response_result['current_step'] = 'post_research_workspace';
        $response_result['module'] = 'technical_seo';
        $response_result['tab'] = 'site_audit';
        $response_result['messages'] = $messages;
        $response_result['assistant_message'] = $assistant_message;
        $primary_guidance_actions = $this->aysa_agent8_bridge_primary_guidance_actions($response_result);
        $api_suggested_actions = isset($response_result['suggested_actions']) && is_array($response_result['suggested_actions'])
            ? $response_result['suggested_actions']
            : array();
        $response_result['suggested_actions'] = !empty($primary_guidance_actions)
            ? $primary_guidance_actions
            : (!empty($api_suggested_actions) ? $this->aysa_agent8_transport_reduce_value($api_suggested_actions, 0, 6) : array());
        if (!empty($primary_guidance_actions) || !empty($api_suggested_actions)) {
            $api_persistent_actions = isset($response_result['persistent_actions']) && is_array($response_result['persistent_actions'])
                ? $response_result['persistent_actions']
                : array();
            $response_result['persistent_actions'] = !empty($api_persistent_actions)
                ? $this->aysa_agent8_transport_reduce_value($api_persistent_actions, 0, 6)
                : array();
        }
        $response_result['approval_request'] = null;
        $response_result['needs_approval'] = false;
        $response_result['action_result'] = $action_result;

        return $response_result;
    }

    private function aysa_agent8_bridge_compact_payload_if_needed($payload, $context = array(), $force = false) {
        if (!is_array($payload)) {
            return $payload;
        }

        if (!$force) {
            $encoded = wp_json_encode($payload);
            if (is_string($encoded) && strlen($encoded) <= 1200000) {
                return $payload;
            }
        }

        if (isset($payload['result']['result']) && is_array($payload['result']['result']) && $this->aysa_agent8_bridge_is_post_research_response($payload['result']['result'])) {
            $payload['result']['result'] = $this->aysa_agent8_bridge_compact_post_research_response($payload['result']['result']);
            $this->aysa_agent8_bridge_log('Compacted async post-research bridge payload.', $context);
            return $payload;
        }

        if (isset($payload['result']) && is_array($payload['result']) && $this->aysa_agent8_bridge_is_post_research_response($payload['result'])) {
            $payload['result'] = $this->aysa_agent8_bridge_compact_post_research_response($payload['result']);
            $this->aysa_agent8_bridge_log('Compacted sync post-research bridge payload.', $context);
            return $payload;
        }

        if ($force) {
            $payload = $this->aysa_agent8_transport_reduce_value($payload, 0, 20);
            $this->aysa_agent8_bridge_log('Reduced non-post-research Agent8 bridge payload.', $context);
        }

        return $payload;
    }

    private function aysa_agent8_bridge_send_json_success($payload, $context = array()) {
        $payload = $this->aysa_agent8_bridge_compact_payload_if_needed($payload, $context);
        $encoded = wp_json_encode(array('success' => true, 'data' => $payload));
        if (!is_string($encoded)) {
            $payload = $this->aysa_agent8_bridge_compact_payload_if_needed($payload, $context, true);
            $encoded = wp_json_encode(array('success' => true, 'data' => $payload));
        }
        if (!is_string($encoded)) {
            $this->aysa_agent8_bridge_log('Agent8 bridge JSON encoding failed.', $context);
            wp_send_json_error(array(
                'message' => 'Agent8 bridge could not encode the response. Please retry.',
                'code' => 'agent8_bridge_json_encode_failed',
            ));
            return;
        }

        wp_send_json_success($payload);
    }

    private function aysa_conversation_async_fail_status($task, $project_id, $wp_user_id, $site_host, $status, $message) {
        if (!is_array($status)) {
            $status = array();
        }
        $status['status'] = 'failed';
        $status['result'] = null;
        $status['error'] = (string) $message;
        $status['updated_at'] = time();
        $status['finished_at'] = time();
        unset($status['_request']);
        $this->aysa_conversation_async_save_status($task, $project_id, $wp_user_id, $site_host, $status);
        return $status;
    }

    private function aysa_conversation_async_maybe_recover_stale($task, $project_id, $wp_user_id, $site_host, $status) {
        if (!is_array($status)) {
            return array();
        }

        $state = (string) ($status['status'] ?? 'idle');
        if (!in_array($state, array('queued', 'running'), true)) {
            return $status;
        }

        $updated_at = intval($status['updated_at'] ?? 0);
        $stale_after = $state === 'running' ? 300 : 20;
        if ($updated_at > 0 && (time() - $updated_at) <= $stale_after) {
            return $status;
        }

        $request = isset($status['_request']) && is_array($status['_request']) ? $status['_request'] : array();
        if ($request === array()) {
            return $this->aysa_conversation_async_fail_status(
                $task,
                $project_id,
                $wp_user_id,
                $site_host,
                $status,
                'Conversation job metadata is missing. Please retry.'
            );
        }

        $attempts = intval($status['attempts'] ?? 0);
        $recovery_count = intval($status['recovery_count'] ?? 0);
        $started_at = intval($status['started_at'] ?? $updated_at);
        $age = $started_at > 0 ? (time() - $started_at) : 0;
        $max_age = $state === 'running' ? 900 : 180;
        if ($recovery_count >= 3 || $age > $max_age) {
            return $this->aysa_conversation_async_fail_status(
                $task,
                $project_id,
                $wp_user_id,
                $site_host,
                $status,
                'Conversation job stalled and was stopped. Please retry.'
            );
        }

        $status['status'] = 'queued';
        $status['updated_at'] = time();
        $status['last_recovered_at'] = time();
        $status['recovery_count'] = $recovery_count + 1;
        $status['error'] = '';
        $this->aysa_conversation_async_save_status($task, $project_id, $wp_user_id, $site_host, $status);

        $this->aysa_conversation_async_schedule_event(array(
            'task' => $task,
            'project_id' => $project_id,
            'wp_user_id' => $wp_user_id,
            'site_host' => $site_host,
            'run_id' => (string) ($status['run_id'] ?? ''),
            'request_hash' => (string) ($status['request_hash'] ?? ''),
            'request' => $request,
        ), 1);

        return $this->aysa_conversation_async_get_status($task, $project_id, $wp_user_id, $site_host);
    }

    private function aysa_conversation_async_schedule_event($payload, $delay_seconds = 1) {
        if (!is_array($payload)) {
            return;
        }
        $timestamp = time() + max(1, intval($delay_seconds));
        wp_schedule_single_event($timestamp, 'aysa_conversation_async_event', array($payload));
        if (function_exists('spawn_cron')) {
            spawn_cron();
        }
    }

    private function aysa_conversation_async_validate_start_request() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            return new WP_Error('invalid_nonce', 'Security check failed.');
        }
        if (!current_user_can('manage_options')) {
            return new WP_Error('insufficient_permissions', 'Insufficient permissions.');
        }
        return true;
    }

    private function aysa_conversation_async_prepare_request($task, $input) {
        $task = sanitize_key((string) $task);
        $wp_user_id = get_current_user_id();
        $project_id = $this->aysa_resolve_installed_project_id(intval($input['project_id'] ?? 0));
        $site_host = isset($input['site_host']) ? sanitize_text_field(wp_unslash((string) $input['site_host'])) : '';
        $conversation_language = $this->aysa_agent8_normalize_conversation_language(
            isset($input['conversation_language']) ? wp_unslash((string) $input['conversation_language']) : '',
            'en'
        );
        $request = array(
            'task' => $task,
            'project_id' => $project_id,
            'wp_user_id' => $wp_user_id,
            'site_host' => $site_host,
            'conversation_language' => $conversation_language,
            'input' => $input,
        );

        if ($task === 'setup') {
            $request['mode'] = isset($input['mode']) ? sanitize_key(wp_unslash((string) $input['mode'])) : 'bootstrap';
            $request['current_step'] = isset($input['current_step']) ? sanitize_key(wp_unslash((string) $input['current_step'])) : 'integrations';
            $request['user_message'] = isset($input['user_message']) ? sanitize_textarea_field(wp_unslash((string) $input['user_message'])) : '';
            $request['request_hash'] = md5(wp_json_encode(array(
                'task' => $task,
                'project_id' => $project_id,
                'site_host' => $site_host,
                'mode' => $request['mode'],
                'current_step' => $request['current_step'],
                'conversation_language' => $conversation_language,
                'user_message' => $request['user_message'],
                'reset_session' => !empty($input['reset_session']),
                'skip_google' => !empty($input['skip_google']),
            )));
            return $request;
        }

        if ($task === 'adjustments') {
            $request['mode'] = isset($input['mode']) ? sanitize_key(wp_unslash((string) $input['mode'])) : 'bootstrap';
            $request['user_message'] = isset($input['user_message']) ? sanitize_textarea_field(wp_unslash((string) $input['user_message'])) : '';
            $request['request_hash'] = md5(wp_json_encode(array(
                'task' => $task,
                'project_id' => $project_id,
                'site_host' => $site_host,
                'mode' => $request['mode'],
                'conversation_language' => $conversation_language,
                'user_message' => $request['user_message'],
                'reset_session' => !empty($input['reset_session']),
                'skip_google' => !empty($input['skip_google']),
                'forced_execution_action' => isset($input['forced_execution_action']) ? sanitize_text_field(wp_unslash((string) $input['forced_execution_action'])) : '',
            )));
            return $request;
        }

        if ($task === 'monitoring') {
            $request['mode'] = isset($input['mode']) ? sanitize_key(wp_unslash((string) $input['mode'])) : 'bootstrap';
            $request['user_message'] = isset($input['user_message']) ? sanitize_textarea_field(wp_unslash((string) $input['user_message'])) : '';
            $request['request_hash'] = md5(wp_json_encode(array(
                'task' => $task,
                'project_id' => $project_id,
                'site_host' => $site_host,
                'mode' => $request['mode'],
                'conversation_language' => $conversation_language,
                'user_message' => $request['user_message'],
                'reset_session' => !empty($input['reset_session']),
                'client_state' => isset($input['client_state']) ? wp_unslash((string) $input['client_state']) : '',
            )));
            return $request;
        }

        if ($task === 'activation') {
            $request['user_message'] = isset($input['user_message']) ? sanitize_textarea_field(wp_unslash((string) $input['user_message'])) : '';
            $request['request_hash'] = md5(wp_json_encode(array(
                'task' => $task,
                'site_host' => $site_host,
                'conversation_language' => $conversation_language,
                'user_message' => $request['user_message'],
            )));
            return $request;
        }

        if ($task === 'agent8') {
            $resolved_project_id = $this->aysa_resolve_technical_project_id($project_id, $site_host);
            if ($resolved_project_id > 0 && $resolved_project_id !== $project_id) {
                aysa_debug_log('[AYSA AGENT8 ASYNC] Normalized async project id from ' . $project_id . ' to ' . $resolved_project_id . ' for host ' . $site_host);
                $project_id = $resolved_project_id;
                $request['project_id'] = $project_id;
            }
            $request['mode'] = isset($input['mode']) ? sanitize_key(wp_unslash((string) $input['mode'])) : 'bootstrap';
            $request['user_message'] = isset($input['user_message']) ? sanitize_textarea_field(wp_unslash((string) $input['user_message'])) : '';
            $request['conversation_id'] = isset($input['conversation_id']) ? sanitize_text_field(wp_unslash((string) $input['conversation_id'])) : '';
            $request['request_epoch'] = max(0, intval($input['request_epoch'] ?? 0));
            $request['action_key'] = isset($input['action_key']) ? sanitize_key(wp_unslash((string) $input['action_key'])) : '';
            $request['approved'] = !empty($input['approved']);
            if (isset($input['action_payload'])) {
                $raw_action_payload = wp_unslash($input['action_payload']);
                if (is_string($raw_action_payload) && $raw_action_payload !== '') {
                    $decoded_action_payload = json_decode($raw_action_payload, true);
                    if (is_array($decoded_action_payload)) {
                        $request['action_payload'] = $decoded_action_payload;
                    }
                } elseif (is_array($raw_action_payload)) {
                    $request['action_payload'] = $raw_action_payload;
                }
            }
            $request['request_hash'] = md5(wp_json_encode(array(
                'task' => $task,
                'project_id' => $project_id,
                'site_host' => $site_host,
                'mode' => $request['mode'],
                'conversation_language' => $conversation_language,
                'request_epoch' => $request['request_epoch'],
                'user_message' => $request['user_message'],
                'conversation_id' => $request['conversation_id'],
                'action_key' => $request['action_key'],
                'approved' => $request['approved'] ? 1 : 0,
                'action_payload' => isset($input['action_payload']) ? wp_unslash((string) $input['action_payload']) : '',
            )));
            return $request;
        }

        return new WP_Error('unsupported_task', 'Unsupported conversation async task.');
    }

    public function handle_conversation_async_start() {
        $validation = $this->aysa_conversation_async_validate_start_request();
        if (is_wp_error($validation)) {
            wp_send_json_error(array('message' => $validation->get_error_message()));
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $task = sanitize_key((string) wp_unslash($_POST['task'] ?? ''));
        if (!in_array($task, $this->aysa_conversation_async_supported_tasks(), true)) {
            wp_send_json_error(array('message' => 'Unsupported conversation task.'));
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $request = $this->aysa_conversation_async_prepare_request($task, $_POST);
        if (is_wp_error($request)) {
            wp_send_json_error(array('message' => $request->get_error_message(), 'code' => $request->get_error_code()));
            return;
        }

        $project_id = intval($request['project_id'] ?? 0);
        $wp_user_id = intval($request['wp_user_id'] ?? get_current_user_id());
        $site_host = (string) ($request['site_host'] ?? '');
        $request_hash = (string) ($request['request_hash'] ?? '');
        $existing = $this->aysa_conversation_async_get_status($task, $project_id, $wp_user_id, $site_host);
        if (
            is_array($existing) &&
            !empty($existing['run_id']) &&
            in_array((string) ($existing['status'] ?? ''), array('queued', 'running'), true) &&
            (string) ($existing['request_hash'] ?? '') === $request_hash
        ) {
            wp_send_json_success($this->aysa_conversation_async_output_status($existing));
            return;
        }

        $run_id = uniqid('conv_', true);
        $status = array(
            'task' => $task,
            'project_id' => $project_id,
            'wp_user_id' => $wp_user_id,
            'site_host' => $site_host,
            'run_id' => $run_id,
            'request_hash' => $request_hash,
            'request_epoch' => max(0, intval($request['request_epoch'] ?? 0)),
            'status' => 'queued',
            'result' => null,
            'error' => '',
            'started_at' => time(),
            'updated_at' => time(),
            'finished_at' => 0,
            'attempts' => 0,
            'recovery_count' => 0,
            'last_recovered_at' => 0,
            'billing_applied' => false,
            '_request' => $request,
        );
        $this->aysa_conversation_async_save_status($task, $project_id, $wp_user_id, $site_host, $status);
        $this->aysa_conversation_async_schedule_event(array(
            'task' => $task,
            'project_id' => $project_id,
            'wp_user_id' => $wp_user_id,
            'site_host' => $site_host,
            'run_id' => $run_id,
            'request_hash' => $request_hash,
            'request' => $request,
        ), 1);

        wp_send_json_success($this->aysa_conversation_async_output_status($status));
    }

    public function handle_conversation_async_status() {
        $validation = $this->aysa_conversation_async_validate_start_request();
        if (is_wp_error($validation)) {
            wp_send_json_error(array('message' => $validation->get_error_message()));
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $task = sanitize_key((string) wp_unslash($_POST['task'] ?? ''));
        if (!in_array($task, $this->aysa_conversation_async_supported_tasks(), true)) {
            wp_send_json_error(array('message' => 'Unsupported conversation task.'));
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $request = $this->aysa_conversation_async_prepare_request($task, $_POST);
        if (is_wp_error($request)) {
            wp_send_json_error(array('message' => $request->get_error_message(), 'code' => $request->get_error_code()));
            return;
        }

        $project_id = intval($request['project_id'] ?? 0);
        $wp_user_id = intval($request['wp_user_id'] ?? get_current_user_id());
        $site_host = (string) ($request['site_host'] ?? '');
        $status = $this->aysa_conversation_async_get_status($task, $project_id, $wp_user_id, $site_host);
        if (empty($status)) {
            $this->aysa_agent8_bridge_send_json_success(array(
                'task' => $task,
                'project_id' => $project_id,
                'status' => 'idle',
                'result' => null,
                'error' => '',
            ), array(
                'task' => $task,
                'run_id' => '',
                'project_id' => $project_id,
                'conversation_id' => (string) ($request['conversation_id'] ?? ''),
                'source' => 'async_status_idle',
            ));
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $requested_run_id = (string) wp_unslash($_POST['run_id'] ?? '');
        if ($requested_run_id !== '' && (string) ($status['run_id'] ?? '') !== $requested_run_id) {
            $this->aysa_agent8_bridge_send_json_success(array(
                'task' => $task,
                'project_id' => $project_id,
                'status' => 'idle',
                'result' => null,
                'error' => '',
            ), array(
                'task' => $task,
                'run_id' => $requested_run_id,
                'project_id' => $project_id,
                'conversation_id' => (string) ($request['conversation_id'] ?? ''),
                'source' => 'async_status_run_mismatch',
            ));
            return;
        }

        if ($task === 'agent8') {
            $requested_epoch = max(0, intval($request['request_epoch'] ?? 0));
            $status_epoch = max(0, intval($status['request_epoch'] ?? 0));
            if ($requested_epoch > 0 && $status_epoch > 0 && $requested_epoch !== $status_epoch) {
                $this->aysa_agent8_bridge_send_json_success(array(
                    'task' => $task,
                    'project_id' => $project_id,
                    'status' => 'idle',
                    'result' => null,
                    'error' => '',
                ), array(
                    'task' => $task,
                    'run_id' => (string) ($status['run_id'] ?? ''),
                    'project_id' => $project_id,
                    'conversation_id' => (string) ($request['conversation_id'] ?? ''),
                    'source' => 'async_status_epoch_mismatch',
                ));
                return;
            }
        }

        $status = $this->aysa_conversation_async_maybe_recover_stale($task, $project_id, $wp_user_id, $site_host, $status);

        $this->aysa_agent8_bridge_send_json_success($this->aysa_conversation_async_output_status($status), array(
            'task' => $task,
            'run_id' => (string) ($status['run_id'] ?? ''),
            'project_id' => $project_id,
            'conversation_id' => (string) ($request['conversation_id'] ?? ''),
            'source' => 'async_status',
        ));
    }

    public function run_conversation_async_event($payload) {
        if (!is_array($payload)) {
            return;
        }

        $task = sanitize_key((string) ($payload['task'] ?? ''));
        $project_id = $this->aysa_resolve_technical_project_id(intval($payload['project_id'] ?? 0));
        $wp_user_id = intval($payload['wp_user_id'] ?? 0);
        $site_host = (string) ($payload['site_host'] ?? '');
        $run_id = (string) ($payload['run_id'] ?? '');
        $request_hash = (string) ($payload['request_hash'] ?? '');
        $request = isset($payload['request']) && is_array($payload['request']) ? $payload['request'] : array();
        if ($run_id === '' || $request_hash === '' || !in_array($task, $this->aysa_conversation_async_supported_tasks(), true)) {
            return;
        }

        if ($wp_user_id > 0 && function_exists('wp_set_current_user')) {
            wp_set_current_user($wp_user_id);
        }

        $status = $this->aysa_conversation_async_get_status($task, $project_id, $wp_user_id, $site_host);
        if (
            empty($status) ||
            (string) ($status['run_id'] ?? '') !== $run_id ||
            (string) ($status['request_hash'] ?? '') !== $request_hash
        ) {
            return;
        }

        $status['status'] = 'running';
        $status['updated_at'] = time();
        $status['attempts'] = intval($status['attempts'] ?? 0) + 1;
        $status['worker_started_at'] = time();
        $status['error'] = '';
        $this->aysa_conversation_async_save_status($task, $project_id, $wp_user_id, $site_host, $status);

        try {
            $result = $this->aysa_conversation_async_execute($task, $request);
        } catch (Throwable $e) {
            $this->aysa_agent8_bridge_log('Agent8 async execution threw.', array(
                'task' => $task,
                'run_id' => $run_id,
                'project_id' => $project_id,
                'conversation_id' => (string) ($request['conversation_id'] ?? ''),
                'source' => 'async_event',
                'error' => $e->getMessage(),
            ));
            $result = new WP_Error('agent8_bridge_exception', 'Agent8 bridge execution failed: ' . $e->getMessage());
        }
        $status['updated_at'] = time();
        $status['finished_at'] = time();
        unset($status['_request']);

        if (is_wp_error($result)) {
            $status['status'] = 'failed';
            $status['result'] = null;
            $status['error'] = $result->get_error_message();
        } else {
            $status['status'] = 'completed';
            $status['result'] = $this->aysa_agent8_bridge_compact_payload_if_needed($result, array(
                'task' => $task,
                'run_id' => $run_id,
                'project_id' => $project_id,
                'conversation_id' => (string) ($request['conversation_id'] ?? ''),
                'source' => 'async_event_result',
            ));
            $status['error'] = '';
            $status['billing_applied'] = true;
        }

        $this->aysa_conversation_async_save_status($task, $project_id, $wp_user_id, $site_host, $status);
    }

    private function aysa_conversation_async_execute($task, $request) {
        switch ($task) {
            case 'setup':
                return $this->aysa_execute_setup_conversation($request);
            case 'adjustments':
                return $this->aysa_execute_adjustments_conversation($request);
            case 'activation':
                return $this->aysa_execute_activation_conversation($request);
            case 'monitoring':
                return $this->aysa_execute_monitoring_conversation($request);
            case 'agent8':
                return $this->aysa_execute_agent8_conversation($request);
        }

        return new WP_Error('unsupported_task', 'Unsupported conversation async task.');
    }

    private function aysa_execute_setup_conversation($input) {
        if (!$this->check_authentication(false)) {
            return new WP_Error('auth_required', 'Authentication required.');
        }

        $request_started_at = microtime(true);
        $key_validation = $this->aysa_validate_test_access_key(
            $this->aysa_get_effective_access_key(isset($input['access_key']) ? sanitize_text_field(wp_unslash((string) $input['access_key'])) : ''),
            isset($input['site_host']) ? sanitize_text_field(wp_unslash((string) $input['site_host'])) : ''
        );
        if (empty($key_validation['valid'])) {
            return new WP_Error('access_key_invalid', (string) ($key_validation['message'] ?? 'Access key is invalid.'));
        }

        $mode = isset($input['mode']) ? sanitize_key(wp_unslash((string) $input['mode'])) : 'bootstrap';
        $current_step = isset($input['current_step']) ? sanitize_key(wp_unslash((string) $input['current_step'])) : 'integrations';
        $conversation_language = $this->aysa_agent8_normalize_conversation_language(
            isset($input['conversation_language']) ? wp_unslash((string) $input['conversation_language']) : '',
            'en'
        );
        $project_id = isset($input['project_id']) ? intval(wp_unslash((string) $input['project_id'])) : intval(get_option('aysa_project_id', 0));
        $user_message = isset($input['user_message']) ? sanitize_textarea_field(wp_unslash((string) $input['user_message'])) : '';
        if ($project_id <= 0) {
            return new WP_Error('missing_project', 'Missing project ID.');
        }

        $auth_manager = Aysa_Auth_Manager::get_instance();
        $auth_status = $auth_manager->is_authenticated();
        $user_id = isset($auth_status['aysa_user_id']) ? intval($auth_status['aysa_user_id']) : 0;
        if ($user_id <= 0) {
            return new WP_Error('invalid_auth_context', 'Invalid authentication context.');
        }

        $onboarding_run_token = $this->aysa_get_or_create_onboarding_run_token($project_id);
        $setup_reset_forced = $this->aysa_is_setup_reset_forced();
        $local_setup_state = $setup_reset_forced ? array() : $this->aysa_get_local_setup_state_for_user($user_id, $project_id);
        $general = isset($local_setup_state['general']) && is_array($local_setup_state['general']) ? $local_setup_state['general'] : array();
        $website = isset($general['website']) ? $this->aysa_setup_normalize_url((string) $general['website']) : '';
        if ($website === '') {
            $website = $this->aysa_setup_normalize_url(home_url('/'));
        }

        $google_connected = false;
        $google_email = '';
        $google_gsc_count = 0;
        $google_ga4_count = 0;
        if (class_exists('Aysa_Google_OAuth_Manager')) {
            $oauth_manager = Aysa_Google_OAuth_Manager::get_instance();
            if (is_object($oauth_manager) && method_exists($oauth_manager, 'get_status_for_project')) {
                $oauth_status = $oauth_manager->get_status_for_project($project_id);
                if (!is_wp_error($oauth_status) && is_array($oauth_status)) {
                    $google_connected = !empty($oauth_status['connected']);
                    $google_email = isset($oauth_status['email']) ? sanitize_email((string) $oauth_status['email']) : '';
                    $google_gsc_count = isset($oauth_status['gsc_sites_count']) ? intval($oauth_status['gsc_sites_count']) : 0;
                    $google_ga4_count = isset($oauth_status['ga4_properties_count']) ? intval($oauth_status['ga4_properties_count']) : 0;
                }
            }
        }

        $endpoint = '/v1/conversation/setup/bootstrap';
        $payload = array(
            'project_id' => $project_id,
            'current_step' => $current_step,
            'reset_session' => !empty($input['reset_session']),
            'skip_google' => !empty($input['skip_google']),
            'conversation_language' => $conversation_language,
            'site_host' => isset($input['site_host']) ? sanitize_text_field(wp_unslash((string) $input['site_host'])) : '',
            'website' => $website,
            'onboarding_run_token' => $onboarding_run_token,
            'wallet_snapshot' => $this->aysa_build_agent_wallet_snapshot($project_id),
            'local_setup_state' => $local_setup_state,
            'ui_context' => array(
                'conversation_language' => $conversation_language,
                'available_languages' => array('en', 'ro', 'bg'),
                'integrations' => array(
                    'terms_url' => 'https://aysa.ai/terms/',
                    'privacy_url' => 'https://aysa.ai/privacy/',
                    'google_connect_available' => true,
                    'google_connect_label' => 'Connect Google',
                    'google_connected' => $google_connected,
                    'google_skipped' => !empty($input['skip_google']),
                    'google_email' => $google_email,
                    'google_gsc_count' => $google_gsc_count,
                    'google_ga4_count' => $google_ga4_count,
                ),
            ),
        );

        if ($mode === 'message') {
            $endpoint = '/v1/conversation/setup/message';
            $payload['user_message'] = $user_message;
        }

        if ($this->aysa_should_queue_setup_competition_suggestion($mode, $current_step, $user_message)) {
            $plugin_url = admin_url('admin.php?page=aysa-agent8-v3-lab');
            $payload['async_notification'] = array(
                'channel' => 'setup_seo_profile',
                'event_key' => 'competition_seed_research_ready',
                'ui_action' => 'resume_setup_onboarding',
                'cta_label' => $conversation_language === 'ro' ? 'Vezi sugestiile' : ($conversation_language === 'bg' ? 'Виж предложенията' : 'See the suggestions'),
                'action_url' => $plugin_url,
                'email_to' => $this->get_notification_target_email(),
                'conversation_language' => $conversation_language,
                'run_token' => $onboarding_run_token,
            );

            $this->aysa_broker_request_async('POST', $endpoint, $payload, 1);

            $assistant_message = $this->aysa_setup_competition_async_ack($conversation_language);
            $email_target = trim((string) ($payload['async_notification']['email_to'] ?? ''));
            return array(
                'result' => array(
                    'ok' => true,
                    'assistant_message' => $assistant_message,
                    'phase' => 'onboarding_competition',
                    'current_step' => 'competition',
                    'next_step' => 'competition',
                    'step_substate' => 'regenerating_proposal',
                    'onboarding_run_token' => $onboarding_run_token,
                    'turn_type' => 'provide_seed_keywords',
                    'intent_family' => 'does_not_know',
                    'assistant_goal' => 'answer_and_reanchor',
                    'next_action' => 'await_async_competition_proposal',
                    'confidence' => 1,
                    'needs_clarification' => false,
                    'reanchor_target' => 'competition',
                    'async_task' => array(
                        'type' => 'competition_seed_research',
                        'status' => 'running',
                        'channel' => 'setup_seo_profile',
                        'event_key' => 'competition_seed_research_ready',
                    ),
                    'notification_policy' => array(
                        'in_app' => true,
                        'email' => $email_target !== '',
                        'channel' => 'setup_seo_profile',
                        'event_key' => 'competition_seed_research_ready',
                        'action_url' => $plugin_url,
                    ),
                    'ready_to_advance' => false,
                    'actions' => array(
                        'executed' => array(),
                        'should_advance' => false,
                    ),
                    'messages' => array(
                        array('role' => 'user', 'message' => $user_message),
                        array('role' => 'assistant', 'message' => $assistant_message),
                    ),
                    'suggested_actions' => array(),
                    'state' => array(
                        'steps' => is_array($local_setup_state) ? $local_setup_state : array(),
                        'meta' => array(
                            'phase' => 'onboarding_competition',
                            'current_step' => 'competition',
                            'next_step' => 'competition',
                            'step_substate' => 'regenerating_proposal',
                            'onboarding_run_token' => $onboarding_run_token,
                            'competition_research_status' => 'running',
                            'intent_family' => 'does_not_know',
                            'assistant_goal' => 'answer_and_reanchor',
                            'last_resolved_action' => 'await_async_competition_proposal',
                            'last_reanchor_target' => 'competition',
                            'turn_confidence' => 1,
                            'needs_clarification' => false,
                        ),
                    ),
                ),
                'access' => $key_validation,
                'queued' => true,
            );
        }

        $broker_started_at = microtime(true);
        $result = $this->aysa_broker_request('POST', $endpoint, $payload, 75);
        $broker_elapsed_ms = (int) round((microtime(true) - $broker_started_at) * 1000);
        if (is_wp_error($result)) {
            return $result;
        }
        if (!is_array($result) || empty($result['ok'])) {
            return new WP_Error('setup_conversation_failed', isset($result['error']) ? (string) $result['error'] : 'Setup conversation request failed.');
        }

        if ($mode === 'message') {
            $this->aysa_wallet_bill_provider_usage(
                $project_id,
                $endpoint,
                isset($result['usage']) && is_array($result['usage']) ? $result['usage'] : array(),
                'setup_conversation_message',
                array(
                    'module' => 'setup_conversation',
                    'source' => 'chat_message',
                )
            );
        }

        if (!empty($result['wp_updates']) && is_array($result['wp_updates'])) {
            $this->aysa_apply_setup_conversation_updates($user_id, $result['wp_updates'], $project_id);
            $updated_local_setup_state = $this->aysa_get_local_setup_state_for_user($user_id, $project_id);
            $result_state = isset($result['state']) && is_array($result['state']) ? $result['state'] : array();
            $result_meta = isset($result_state['meta']) && is_array($result_state['meta']) ? $result_state['meta'] : array();
            $result_steps = isset($result_state['steps']) && is_array($result_state['steps']) ? $result_state['steps'] : array();
            $google_skipped = !empty($result_steps['integrations']['google_skip']) || !empty($result_meta['google_optional_skipped']);
            $this->aysa_sync_setup_state_to_client_rag(
                $project_id,
                $conversation_language,
                $updated_local_setup_state,
                $google_connected,
                $google_skipped,
                $result_meta
            );
        }

        if (defined('WP_DEBUG') && WP_DEBUG) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Release-reviewed diagnostic only; the message contains no credentials or request payloads and runs only in opt-in debugging or the administrative uninstall lifecycle.
            error_log(sprintf(
                'AYSA setup conversation timings: broker=%dms total=%dms mode=%s project=%d',
                $broker_elapsed_ms,
                (int) round((microtime(true) - $request_started_at) * 1000),
                (string) $mode,
                (int) $project_id
            ));
        }

        return array(
            'result' => $result,
            'access' => $key_validation,
        );
    }

    private function aysa_execute_adjustments_conversation($input) {
        if (!$this->check_authentication(false)) {
            return new WP_Error('auth_required', 'Authentication required.');
        }

        $key_validation = $this->aysa_validate_test_access_key(
            $this->aysa_get_effective_access_key(isset($input['access_key']) ? sanitize_text_field(wp_unslash((string) $input['access_key'])) : ''),
            isset($input['site_host']) ? sanitize_text_field(wp_unslash((string) $input['site_host'])) : ''
        );
        if (empty($key_validation['valid'])) {
            return new WP_Error('access_key_invalid', (string) ($key_validation['message'] ?? 'Access key is invalid.'));
        }

        $mode = isset($input['mode']) ? sanitize_key(wp_unslash((string) $input['mode'])) : 'bootstrap';
        $conversation_language = isset($input['conversation_language']) ? sanitize_key(wp_unslash((string) $input['conversation_language'])) : 'en';
        if (!in_array($conversation_language, array('en', 'ro', 'bg'), true)) {
            $conversation_language = 'en';
        }
        $project_id = isset($input['project_id']) ? intval(wp_unslash((string) $input['project_id'])) : intval(get_option('aysa_project_id', 0));
        $user_message = isset($input['user_message']) ? sanitize_textarea_field(wp_unslash((string) $input['user_message'])) : '';

        if ($project_id <= 0) {
            return new WP_Error('missing_project', 'Missing project ID.');
        }

        $auth_manager = Aysa_Auth_Manager::get_instance();
        $auth_status = $auth_manager->is_authenticated();
        $user_id = isset($auth_status['aysa_user_id']) ? intval($auth_status['aysa_user_id']) : 0;
        if ($user_id <= 0) {
            return new WP_Error('invalid_auth_context', 'Invalid authentication context.');
        }

        $setup_reset_forced = $this->aysa_is_setup_reset_forced();
        $local_setup_state = $setup_reset_forced ? array() : $this->aysa_get_local_setup_state_for_user($user_id, $project_id);
        $general = isset($local_setup_state['general']) && is_array($local_setup_state['general']) ? $local_setup_state['general'] : array();
        $website = isset($general['website']) ? $this->aysa_setup_normalize_url((string) $general['website']) : '';
        if ($website === '') {
            $website = $this->aysa_setup_normalize_url(home_url('/'));
        }

        $google_connected = false;
        $google_email = '';
        $google_gsc_count = 0;
        $google_ga4_count = 0;
        if (class_exists('Aysa_Google_OAuth_Manager')) {
            $oauth_manager = Aysa_Google_OAuth_Manager::get_instance();
            if (is_object($oauth_manager) && method_exists($oauth_manager, 'get_status_for_project')) {
                $oauth_status = $oauth_manager->get_status_for_project($project_id);
                if (!is_wp_error($oauth_status) && is_array($oauth_status)) {
                    $google_connected = !empty($oauth_status['connected']);
                    $google_email = isset($oauth_status['email']) ? sanitize_email((string) $oauth_status['email']) : '';
                    $google_gsc_count = isset($oauth_status['gsc_sites_count']) ? intval($oauth_status['gsc_sites_count']) : 0;
                    $google_ga4_count = isset($oauth_status['ga4_properties_count']) ? intval($oauth_status['ga4_properties_count']) : 0;
                }
            }
        }

        $research_snapshot = $setup_reset_forced ? array() : $this->aysa_adjustments_get_research_snapshot($project_id, $user_id);
        $research_snapshot_for_ai = $setup_reset_forced ? array() : $this->aysa_adjustments_reduce_research_snapshot_for_ai($research_snapshot);
        $client_state = array();
        if (!$setup_reset_forced && isset($input['client_state'])) {
            $decoded_client_state = json_decode(wp_unslash((string) $input['client_state']), true);
            if (is_array($decoded_client_state)) {
                $client_state = $decoded_client_state;
            }
        }
        $client_local_setup_state = isset($client_state['local_setup_state']) && is_array($client_state['local_setup_state'])
            ? $client_state['local_setup_state']
            : array();
        if ($client_local_setup_state !== array()) {
            $local_setup_state = $this->aysa_merge_local_setup_state_fallback($local_setup_state, $client_local_setup_state);
        }
        $forced_execution_action = isset($input['forced_execution_action']) ? sanitize_text_field(wp_unslash((string) $input['forced_execution_action'])) : '';

        if ($mode === 'message' && $forced_execution_action !== '') {
            $result = array(
                'ok' => true,
                'state' => is_array($client_state) ? $client_state : array(),
                'messages' => array(),
            );
            $execution_meta = $this->aysa_adjustments_execute_research_action($forced_execution_action, $project_id, $user_id, $conversation_language);
            $research_snapshot = $this->aysa_adjustments_get_research_snapshot($project_id, $user_id);
            $assistant_message = !empty($execution_meta['assistant_message']) ? (string) $execution_meta['assistant_message'] : '';
            if ($user_message !== '') {
                $result['messages'][] = array('role' => 'user', 'message' => $user_message);
            }
            if ($assistant_message !== '') {
                $result['messages'][] = array('role' => 'assistant', 'message' => $assistant_message);
                $result['assistant_message'] = $assistant_message;
            }
            if (!empty($execution_meta['suggested_actions']) && is_array($execution_meta['suggested_actions'])) {
                $result['suggested_actions'] = $execution_meta['suggested_actions'];
            }
            if (!empty($execution_meta['ui_action'])) {
                $result['ui_action'] = (string) $execution_meta['ui_action'];
            }
            if (!empty($execution_meta['payment_link'])) {
                $result['payment_link'] = (string) $execution_meta['payment_link'];
            }
            if (!isset($result['state']['context']) || !is_array($result['state']['context'])) {
                $result['state']['context'] = array();
            }
            if (!isset($result['state']['meta']) || !is_array($result['state']['meta'])) {
                $result['state']['meta'] = array();
            }
            $result['state']['context']['research_snapshot'] = $research_snapshot;
            if (!empty($execution_meta['module'])) {
                $result['state']['meta']['active_research_module'] = (string) $execution_meta['module'];
            }
            if (!empty($execution_meta['state_meta']) && is_array($execution_meta['state_meta'])) {
                foreach ($execution_meta['state_meta'] as $meta_key => $meta_value) {
                    if (!is_string($meta_key) || $meta_key === '') {
                        continue;
                    }
                    $result['state']['meta'][$meta_key] = $meta_value;
                }
            }
            $result['research_snapshot'] = $research_snapshot;
            $result['workspace_artifact'] = $this->aysa_adjustments_build_research_workspace_artifact($research_snapshot, $conversation_language);

            return array(
                'result' => $result,
                'access' => $key_validation,
            );
        }

        $endpoint = '/v1/conversation/adjustments/bootstrap';
        $payload = array(
            'project_id' => $project_id,
            'aysa_user_id' => $user_id,
            'conversation_language' => $conversation_language,
            'reset_session' => !empty($input['reset_session']),
            'skip_google' => !empty($input['skip_google']),
            'site_host' => isset($input['site_host']) ? sanitize_text_field(wp_unslash((string) $input['site_host'])) : '',
            'website' => $website,
            'wallet_snapshot' => $this->aysa_build_agent_wallet_snapshot($project_id),
            'local_setup_state' => $local_setup_state,
            'research_snapshot' => $research_snapshot_for_ai,
            'ui_context' => array(
                'conversation_language' => $conversation_language,
                'available_languages' => array('en', 'ro', 'bg'),
                'phase' => 'agent_orchestration',
                'integrations' => array(
                    'terms_url' => 'https://aysa.ai/terms/',
                    'privacy_url' => 'https://aysa.ai/privacy/',
                    'google_connect_available' => true,
                    'google_connect_label' => 'Connect Google',
                    'google_connected' => $google_connected,
                    'google_skipped' => !empty($input['skip_google']),
                    'google_email' => $google_email,
                    'google_gsc_count' => $google_gsc_count,
                    'google_ga4_count' => $google_ga4_count,
                ),
            ),
            'state' => $client_state,
        );
        if ($mode === 'message') {
            $endpoint = '/v1/conversation/adjustments/message';
            $payload['user_message'] = $user_message;
        }

        $result = $this->aysa_broker_request('POST', $endpoint, $payload, 75);
        if (is_wp_error($result)) {
            return $result;
        }
        if (!is_array($result) || empty($result['ok'])) {
            return new WP_Error('adjustments_conversation_failed', isset($result['error']) ? (string) $result['error'] : 'Adjustments conversation request failed.');
        }

        if ($mode === 'message') {
            $this->aysa_wallet_bill_provider_usage(
                $project_id,
                $endpoint,
                isset($result['usage']) && is_array($result['usage']) ? $result['usage'] : array(),
                'adjustments_conversation_message',
                array(
                    'module' => 'adjustments_conversation',
                    'source' => 'chat_message',
                )
            );
        }

        $execution_action = (string) ($result['execution_action'] ?? '');
        if ($execution_action !== '') {
            $execution_meta = $this->aysa_adjustments_execute_research_action($execution_action, $project_id, $user_id, $conversation_language);
            $research_snapshot = $this->aysa_adjustments_get_research_snapshot($project_id, $user_id);
            if (!empty($execution_meta['assistant_message'])) {
                $result['assistant_message'] = $execution_meta['assistant_message'];
            }
            if (!empty($execution_meta['suggested_actions']) && is_array($execution_meta['suggested_actions'])) {
                $result['suggested_actions'] = $execution_meta['suggested_actions'];
            }
            if (!empty($execution_meta['ui_action'])) {
                $result['ui_action'] = (string) $execution_meta['ui_action'];
            }
            if (!empty($execution_meta['payment_link'])) {
                $result['payment_link'] = (string) $execution_meta['payment_link'];
            }
            if (!isset($result['state']) || !is_array($result['state'])) {
                $result['state'] = array();
            }
            if (!isset($result['state']['context']) || !is_array($result['state']['context'])) {
                $result['state']['context'] = array();
            }
            if (!isset($result['state']['meta']) || !is_array($result['state']['meta'])) {
                $result['state']['meta'] = array();
            }
            $result['state']['context']['research_snapshot'] = $research_snapshot;
            if (!empty($execution_meta['module'])) {
                $result['state']['meta']['active_research_module'] = (string) $execution_meta['module'];
            }
            if (!empty($execution_meta['state_meta']) && is_array($execution_meta['state_meta'])) {
                foreach ($execution_meta['state_meta'] as $meta_key => $meta_value) {
                    if (!is_string($meta_key) || $meta_key === '') {
                        continue;
                    }
                    $result['state']['meta'][$meta_key] = $meta_value;
                }
            }
            if ($execution_action === 'keyword_research_sync' && empty($result['state']['meta']['keyword_walkthrough_stage'])) {
                $result['state']['meta']['keyword_walkthrough_stage'] = 'ranking';
            }
        } else {
            $research_snapshot = $this->aysa_adjustments_get_research_snapshot($project_id, $user_id);
            if (isset($result['state']) && is_array($result['state'])) {
                if (!isset($result['state']['context']) || !is_array($result['state']['context'])) {
                    $result['state']['context'] = array();
                }
                $result['state']['context']['research_snapshot'] = $research_snapshot;
            }
        }
        $result['research_snapshot'] = $research_snapshot;
        $result['workspace_artifact'] = $this->aysa_adjustments_build_research_workspace_artifact($research_snapshot, $conversation_language);

        if (isset($result['state']['context']['local_setup_state']) && is_array($result['state']['context']['local_setup_state'])) {
            $this->aysa_persist_local_setup_state_snapshot($user_id, $result['state']['context']['local_setup_state'], $project_id);
        }

        return array(
            'result' => $result,
            'access' => $key_validation,
        );
    }

    private function aysa_execute_monitoring_conversation($input) {
        if (!$this->check_authentication(false)) {
            return new WP_Error('auth_required', 'Authentication required.');
        }

        $key_validation = $this->aysa_validate_test_access_key(
            $this->aysa_get_effective_access_key(isset($input['access_key']) ? sanitize_text_field(wp_unslash((string) $input['access_key'])) : ''),
            isset($input['site_host']) ? sanitize_text_field(wp_unslash((string) $input['site_host'])) : ''
        );
        if (empty($key_validation['valid'])) {
            return new WP_Error('access_key_invalid', (string) ($key_validation['message'] ?? 'Access key is invalid.'));
        }

        $mode = isset($input['mode']) ? sanitize_key(wp_unslash((string) $input['mode'])) : 'bootstrap';
        $conversation_language = isset($input['conversation_language']) ? sanitize_key(wp_unslash((string) $input['conversation_language'])) : 'en';
        if (!in_array($conversation_language, array('en', 'ro', 'bg'), true)) {
            $conversation_language = 'en';
        }
        $project_id = isset($input['project_id']) ? intval(wp_unslash((string) $input['project_id'])) : intval(get_option('aysa_project_id', 0));
        $user_message = isset($input['user_message']) ? sanitize_textarea_field(wp_unslash((string) $input['user_message'])) : '';
        if ($project_id <= 0) {
            return new WP_Error('missing_project', 'Missing project ID.');
        }

        $auth_manager = Aysa_Auth_Manager::get_instance();
        $auth_status = $auth_manager->is_authenticated();
        $user_id = isset($auth_status['aysa_user_id']) ? intval($auth_status['aysa_user_id']) : 0;
        if ($user_id <= 0) {
            return new WP_Error('invalid_auth_context', 'Invalid authentication context.');
        }

        $client_state = array();
        if (isset($input['client_state'])) {
            $decoded_client_state = json_decode(wp_unslash((string) $input['client_state']), true);
            if (is_array($decoded_client_state)) {
                $client_state = $decoded_client_state;
            }
        }

        $local_setup_state = $this->aysa_get_local_setup_state_for_user($user_id, $project_id);
        $general = isset($local_setup_state['general']) && is_array($local_setup_state['general']) ? $local_setup_state['general'] : array();
        $website = isset($general['website']) ? $this->aysa_setup_normalize_url((string) $general['website']) : '';
        if ($website === '') {
            $website = $this->aysa_setup_normalize_url(home_url('/'));
        }

        $monitoring_state = array(
            'tracked_count' => intval($client_state['tracked_count'] ?? 0),
            'refreshed_count' => intval($client_state['refreshed_count'] ?? 0),
            'stale_count' => intval($client_state['stale_count'] ?? 0),
            'stale_preview' => isset($client_state['stale_preview']) && is_array($client_state['stale_preview']) ? $client_state['stale_preview'] : array(),
            'job_status' => sanitize_text_field((string) ($client_state['job_status'] ?? 'idle')),
            'job_processed' => intval($client_state['job_processed'] ?? 0),
            'job_total' => intval($client_state['job_total'] ?? 0),
            'wallet_balance' => isset($client_state['wallet_balance']) ? intval($client_state['wallet_balance']) : null,
            'wallet_spendable' => isset($client_state['wallet_spendable']) ? intval($client_state['wallet_spendable']) : null,
            'wallet_reserved_links' => isset($client_state['wallet_reserved_links']) ? intval($client_state['wallet_reserved_links']) : null,
        );

        $endpoint = '/v1/conversation/monitoring/bootstrap';
        $payload = array(
            'project_id' => $project_id,
            'conversation_language' => $conversation_language,
            'reset_session' => !empty($input['reset_session']),
            'site_host' => isset($input['site_host']) ? sanitize_text_field(wp_unslash((string) $input['site_host'])) : '',
            'website' => $website,
            'local_setup_state' => $local_setup_state,
            'ui_context' => array(
                'conversation_language' => $conversation_language,
                'available_languages' => array('en', 'ro', 'bg'),
                'page' => 'monitoring_analytics',
            ),
            'state' => array(
                'monitoring' => $monitoring_state,
            ),
        );
        if ($mode === 'message') {
            $endpoint = '/v1/conversation/monitoring/message';
            $payload['user_message'] = $user_message;
        }

        $result = $this->aysa_broker_request('POST', $endpoint, $payload, 45);
        if (is_wp_error($result)) {
            return $result;
        }
        if (!is_array($result) || empty($result['ok'])) {
            return new WP_Error('monitoring_conversation_failed', isset($result['error']) ? (string) $result['error'] : 'Monitoring conversation request failed.');
        }

        if ($mode === 'message') {
            $this->aysa_wallet_bill_provider_usage(
                $project_id,
                $endpoint,
                isset($result['usage']) && is_array($result['usage']) ? $result['usage'] : array(),
                'monitoring_conversation_message',
                array(
                    'module' => 'monitoring_conversation',
                    'source' => 'chat_message',
                )
            );
        }

        return array(
            'result' => $result,
            'access' => $key_validation,
        );
    }

    private function aysa_execute_activation_conversation($input) {
        $conversation_language = isset($input['conversation_language']) ? sanitize_key(wp_unslash((string) $input['conversation_language'])) : 'en';
        if (!in_array($conversation_language, array('en', 'ro', 'bg'), true)) {
            $conversation_language = 'en';
        }
        $user_message = isset($input['user_message']) ? sanitize_textarea_field(wp_unslash((string) $input['user_message'])) : '';
        $site_host = isset($input['site_host']) ? sanitize_text_field(wp_unslash((string) $input['site_host'])) : '';

        $access_key_candidate = trim($user_message);
        if ($access_key_candidate !== '') {
            $validation = $this->aysa_validate_test_access_key($access_key_candidate, $site_host);
            if (!empty($validation['valid'])) {
                update_option($this->aysa_test_access_key_option_name(), $access_key_candidate, false);

                if (!empty($validation['project_id'])) {
                    update_option('aysa_project_id', intval($validation['project_id']), false);
                }
                if (!empty($validation['website_id'])) {
                    update_option('aysa_website_id', intval($validation['website_id']), false);
                }
                $this->aysa_store_projection_sync_secret_from_validation($validation);

                $auth_bootstrap = null;
                if (!class_exists('Aysa_Auth_Manager')) {
                    require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
                }
                if (class_exists('Aysa_Auth_Manager')) {
                    $auth_manager = Aysa_Auth_Manager::get_instance();
                    $auth_bootstrap = $auth_manager->verify_and_store_secret_key($access_key_candidate, $site_host);
                    if (is_array($auth_bootstrap) && !empty($auth_bootstrap['success'])) {
                        update_option('aysa_user_secret_key', $access_key_candidate, false);
                        update_option('aysa_user_id', intval($auth_bootstrap['aysa_user_id'] ?? 0), false);
                        update_option('aysa_auth_domain', $site_host !== '' ? $site_host : wp_parse_url(home_url(), PHP_URL_HOST), false);
                    }
                }

                if (!empty($validation['plan_code'])) {
                    update_option('aysa_wallet_default_plan', sanitize_key((string) $validation['plan_code']), false);
                    if (class_exists('Aysa_Wallet_Manager')) {
                        $project_id = intval(get_option('aysa_project_id', 0));
                        if ($project_id > 0) {
                            Aysa_Wallet_Manager::get_instance()->apply_plan_to_project(
                                $project_id,
                                sanitize_key((string) $validation['plan_code']),
                                false
                            );
                        }
                    }
                }

                if (is_array($auth_bootstrap) && empty($auth_bootstrap['success'])) {
                    $validation['auth_bootstrap_warning'] = (string) ($auth_bootstrap['error'] ?? 'Authentication bootstrap failed after access key verification.');
                }

                $success_message = match ($conversation_language) {
                    'ro' => 'Cheia a fost validată. Mulțumim. Activez pluginul și continuăm automat.',
                    'bg' => 'Ключът е валидиран. Благодаря. Активирам плъгина и продължаваме автоматично.',
                    default => 'The key has been validated. Thank you. I am activating the plugin and we will continue automatically.',
                };

                return array(
                    'ok' => true,
                    'assistant_message' => $success_message,
                    'activation_verified' => true,
                    'redirect_url' => admin_url('admin.php?page=aysa'),
                    'validation' => $validation,
                );
            }
        }

        $result = $this->aysa_broker_request('POST', '/v1/conversation/activation/message', array(
            'conversation_language' => $conversation_language,
            'user_message' => $user_message,
            'site_host' => $site_host,
            'activation_url' => 'https://app.aysa.ai/websites',
        ), 45);

        if (is_wp_error($result)) {
            return $result;
        }
        if (!is_array($result) || empty($result['ok'])) {
            return new WP_Error('activation_conversation_failed', isset($result['error']) ? (string) $result['error'] : 'Activation conversation request failed.');
        }

        return $result;
    }

    private function aysa_execute_agent8_conversation($input) {
        $request_started_at = microtime(true);
        $bundle_build_ms = 0.0;
        $broker_request_ms = 0.0;
        $local_persistence_ms = 0.0;
        $payload_size_bytes = 0;
        $execution_path = isset($input['_agent8_execution_path'])
            ? sanitize_key(wp_unslash((string) $input['_agent8_execution_path']))
            : 'async';
        if (!in_array($execution_path, array('sync', 'async'), true)) {
            $execution_path = 'async';
        }

        $mode = isset($input['mode']) ? sanitize_key(wp_unslash((string) $input['mode'])) : 'bootstrap';
        $requested_mode = $mode;
        $conversation_language = $this->aysa_agent8_normalize_conversation_language(
            isset($input['conversation_language']) ? wp_unslash((string) $input['conversation_language']) : '',
            'en'
        );

        $wp_project_id = intval(get_option('aysa_project_id', 0));
        $posted_project_id = isset($input['project_id']) ? intval(wp_unslash((string) $input['project_id'])) : 0;
        $site_host = isset($input['site_host']) ? sanitize_text_field(wp_unslash((string) $input['site_host'])) : '';
        $user_message = isset($input['user_message']) ? sanitize_textarea_field(wp_unslash((string) $input['user_message'])) : '';
        $client_state = array();
        if (isset($input['client_state'])) {
            $decoded_client_state = json_decode(wp_unslash((string) $input['client_state']), true);
            if (is_array($decoded_client_state)) {
                $client_state = $decoded_client_state;
            }
        }
        $wp_user_id = intval($input['wp_user_id'] ?? get_current_user_id());
        $auth_manager = class_exists('Aysa_Auth_Manager') ? Aysa_Auth_Manager::get_instance() : null;
        $auth_status = is_object($auth_manager) && method_exists($auth_manager, 'is_authenticated')
            ? $auth_manager->is_authenticated()
            : array();
        $setup_user_id = isset($auth_status['aysa_user_id']) ? intval($auth_status['aysa_user_id']) : 0;
        if ($setup_user_id <= 0) {
            $setup_user_id = intval(get_option('aysa_user_id', 0));
        }
        if ($setup_user_id <= 0) {
            $setup_user_id = intval($this->get_user_id());
        }
        if ($setup_user_id <= 0) {
            $setup_user_id = intval($this->aysa_get_authenticated_external_user_id());
        }

        $key_validation = null;
        $has_valid_access_key = $this->aysa_is_access_key_valid_globally($key_validation);
        $canonical_project_id = !empty($has_valid_access_key)
            ? $this->aysa_resolve_agent8_canonical_project_id($posted_project_id > 0 ? $posted_project_id : $wp_project_id, $site_host, is_array($key_validation) ? $key_validation : array())
            : 0;
        $agent_project_id = !empty($has_valid_access_key)
            ? ($canonical_project_id > 0 ? $canonical_project_id : ($posted_project_id > 0 ? $posted_project_id : $wp_project_id))
            : 0;
        $saved_session = $this->aysa_agent8_load_session($wp_user_id, $site_host, $conversation_language);
        $request_epoch = max(0, intval($input['request_epoch'] ?? 0));
        $saved_request_epoch = max(0, intval($saved_session['request_epoch'] ?? 0));
        if ($request_epoch > $saved_request_epoch) {
            $saved_session['request_epoch'] = $request_epoch;
            update_user_meta($wp_user_id, $this->aysa_agent8_storage_key($wp_user_id, $site_host), $saved_session);
        }
        $wallet_snapshot = !empty($has_valid_access_key)
            ? $this->aysa_build_agent_wallet_snapshot($agent_project_id)
            : array();

        if ($mode === 'reset') {
            $this->aysa_agent8_clear_session($wp_user_id, $site_host);
            $client_state = array();
            $saved_session = array();
            $mode = 'bootstrap';
            $input['force_fresh_bootstrap'] = true;
            $input['conversation_id'] = '';
        }

        if ($mode === 'bootstrap') {
            $client_state = array();
        }

        $website = $this->aysa_setup_normalize_url(home_url('/'));
        $payload = array(
            'project_id' => $agent_project_id,
            'canonical_project_id' => $agent_project_id,
            'wp_project_id' => $wp_project_id,
            'conversation_language' => $conversation_language,
            'site_host' => $site_host,
            'website' => $website,
            'wp_user_id' => $wp_user_id,
            'aysa_user_id' => $setup_user_id,
            'return_url' => admin_url('admin.php?page=aysa-agent8-v3-lab'),
            'agent8_url' => admin_url('admin.php?page=aysa-agent8-v3-lab'),
            'email_to' => $this->get_notification_target_email(),
            'access_key_validation' => array(
                'valid' => !empty($has_valid_access_key),
                'project_id' => intval($key_validation['project_id'] ?? 0),
                'website_id' => intval($key_validation['website_id'] ?? 0),
                'canonical_project_id' => intval($canonical_project_id),
                'plan_code' => (string) ($key_validation['plan_code'] ?? ''),
            ),
        );
        if (!empty($input['force_fresh_bootstrap'])) {
            $payload['force_fresh_bootstrap'] = true;
        }
        $force_fresh_bootstrap = !empty($input['force_fresh_bootstrap']);
        $saved_conversation_id = $force_fresh_bootstrap ? '' : (isset($saved_session['conversation_id']) ? trim((string) $saved_session['conversation_id']) : '');
        $posted_conversation_id = $force_fresh_bootstrap ? '' : (isset($input['conversation_id']) ? sanitize_text_field(wp_unslash((string) $input['conversation_id'])) : '');
        $conversation_id = $posted_conversation_id !== '' ? $posted_conversation_id : $saved_conversation_id;
        if ($conversation_id !== '') {
            $payload['conversation_id'] = $conversation_id;
        }
        if ($mode === 'message') {
            $payload['user_message'] = $user_message;
            if ($client_state !== array()) {
                $payload['client_state'] = $client_state;
            }
        } elseif ($mode === 'action' || $mode === 'approval') {
            $payload['action_key'] = isset($input['action_key']) ? sanitize_key(wp_unslash((string) $input['action_key'])) : '';
            if ($mode === 'approval') {
                $payload['approved'] = !empty($input['approved']);
            }
            if (isset($input['action_payload'])) {
                $raw_action_payload = wp_unslash($input['action_payload']);
                if (is_string($raw_action_payload) && $raw_action_payload !== '') {
                    $decoded_action_payload = json_decode($raw_action_payload, true);
                    if (is_array($decoded_action_payload)) {
                        $payload['action_payload'] = $decoded_action_payload;
                    }
                } elseif (is_array($raw_action_payload)) {
                    $payload['action_payload'] = $raw_action_payload;
                }
            }
        }

        if ($mode !== 'bootstrap' && !empty($has_valid_access_key)) {
            $action_key = isset($payload['action_key']) ? (string) $payload['action_key'] : '';
            $bundle_started_at = microtime(true);
            $payload = array_merge($payload, $this->aysa_agent8_runtime_bundle_for_request($wp_project_id, $mode, $user_message, $action_key));
            $bundle_build_ms = (microtime(true) - $bundle_started_at) * 1000;
        }
        $payload_size_bytes = strlen((string) wp_json_encode($payload));

        $bundle_action_key = isset($payload['action_key']) ? sanitize_key((string) $payload['action_key']) : '';
        if ($mode === 'action' && $this->aysa_adjustments_is_site_audit_bundle_action_key($bundle_action_key)) {
            $approval_request = $this->aysa_adjustments_build_site_audit_bundle_approval_request(
                $bundle_action_key,
                isset($payload['action_payload']) && is_array($payload['action_payload']) ? $payload['action_payload'] : array()
            );
            if (is_array($approval_request)) {
                if ($wp_project_id > 0) {
                    $this->aysa_adjustments_update_site_audit_action_bundle_state($wp_project_id, $bundle_action_key, array(
                        'approval_state' => 'suggested',
                        'last_sent_to_agent8_at' => current_time('mysql'),
                    ));
                }

                $response_result = array(
                    'ok' => true,
                    'state' => isset($saved_session['state']) && is_array($saved_session['state']) ? $saved_session['state'] : array(),
                    'conversation_id' => $conversation_id,
                    'canonical_project_id' => intval($agent_project_id),
                    'wp_project_id' => intval($wp_project_id),
                    'phase' => 'conversation',
                    'current_step' => 'technical_site_audit_bundle_approval',
                    'owner' => 'conversation',
                    'capability_level' => 'Conversational',
                    'agent_version' => '0.7.9',
                    'messages' => array(
                        array(
                            'role' => 'assistant',
                            'message' => 'Am pregătit bundle-ul tehnic și aștept acordul tău explicit înainte să rulez pregătirea în Agent8.',
                        ),
                    ),
                    'assistant_message' => 'Am pregătit bundle-ul tehnic și aștept acordul tău explicit înainte să rulez pregătirea în Agent8.',
                    'suggested_actions' => array(),
                    'approval_request' => $approval_request,
                    'proposed_action' => null,
                    'action_result' => null,
                    'billing' => null,
                    'needs_approval' => true,
                    'form_projection' => null,
                    'activity_feed' => array(),
                    'contract_status' => '',
                    'contract_issue_code' => '',
                    'contract_issue_details' => array(),
                );

                $response_result = $this->aysa_agent8_attach_plugin_timings(
                    $response_result,
                    $execution_path,
                    $bundle_build_ms,
                    $broker_request_ms,
                    $local_persistence_ms,
                    $request_started_at,
                    $payload_size_bytes
                );
                $this->aysa_agent8_save_session($wp_user_id, $site_host, $conversation_language, $response_result, $request_epoch);
                return array(
                    'result' => $response_result,
                    'wallet_snapshot' => $wallet_snapshot,
                );
            }
        }

        if ($mode === 'approval' && $this->aysa_adjustments_is_site_audit_bundle_action_key($bundle_action_key)) {
            if (empty($payload['approved'])) {
                if ($wp_project_id > 0) {
                    $this->aysa_adjustments_update_site_audit_action_bundle_state($wp_project_id, $bundle_action_key, array(
                        'approval_state' => 'dismissed',
                        'dismissed_at' => current_time('mysql'),
                    ));
                }
                $response_result = array(
                    'ok' => true,
                    'state' => isset($saved_session['state']) && is_array($saved_session['state']) ? $saved_session['state'] : array(),
                    'conversation_id' => $conversation_id,
                    'canonical_project_id' => intval($agent_project_id),
                    'wp_project_id' => intval($wp_project_id),
                    'phase' => 'conversation',
                    'current_step' => 'technical_site_audit',
                    'owner' => 'conversation',
                    'capability_level' => 'Conversational',
                    'agent_version' => '0.7.9',
                    'messages' => array(
                        array(
                            'role' => 'assistant',
                            'message' => 'Am oprit bundle-ul tehnic pentru moment. Îl poți relansa oricând din Site Audit sau Agent8.',
                        ),
                    ),
                    'assistant_message' => 'Am oprit bundle-ul tehnic pentru moment. Îl poți relansa oricând din Site Audit sau Agent8.',
                    'suggested_actions' => array(),
                    'approval_request' => null,
                    'proposed_action' => null,
                    'action_result' => null,
                    'billing' => null,
                    'needs_approval' => false,
                    'form_projection' => null,
                    'activity_feed' => array(),
                    'contract_status' => '',
                    'contract_issue_code' => '',
                    'contract_issue_details' => array(),
                );

                $response_result = $this->aysa_agent8_attach_plugin_timings(
                    $response_result,
                    $execution_path,
                    $bundle_build_ms,
                    $broker_request_ms,
                    $local_persistence_ms,
                    $request_started_at,
                    $payload_size_bytes
                );
                $this->aysa_agent8_save_session($wp_user_id, $site_host, $conversation_language, $response_result, $request_epoch);
                return array(
                    'result' => $response_result,
                    'wallet_snapshot' => $wallet_snapshot,
                );
            }

            $mode = 'action';
        }

        $endpoint = match ($mode) {
            'bootstrap' => '/v1/agent8/bootstrap',
            'approval' => '/v1/agent8/approval',
            'action' => '/v1/agent8/action',
            default => '/v1/agent8/message',
        };
        $broker_started_at = microtime(true);
        $result = $this->aysa_broker_request('POST', $endpoint, $payload, 75);
        $broker_request_ms = (microtime(true) - $broker_started_at) * 1000;
        if (is_wp_error($result)) {
            return $result;
        }
        if (!is_array($result) || empty($result['ok'])) {
            return new WP_Error('agent8_request_failed', isset($result['error']) ? (string) $result['error'] : 'Agent 8 request failed.');
        }

        $local_persistence_started_at = microtime(true);

        if (
            isset($result['action_result']['activation_validation'])
            && is_array($result['action_result']['activation_validation'])
            && !empty($result['action_result']['activation_validation']['valid'])
            && !empty($result['action_result']['raw_access_key'])
        ) {
            $this->aysa_store_verified_access_key(
                $result['action_result']['activation_validation'],
                (string) $result['action_result']['raw_access_key'],
                $site_host
            );
            if ($setup_user_id > 0 || $wp_project_id > 0) {
                $this->aysa_reset_local_setup_state($setup_user_id, $wp_project_id);
            }
        }

        if (
            $setup_user_id > 0
            && isset($result['state']['context']['local_setup_state'])
            && is_array($result['state']['context']['local_setup_state'])
        ) {
            $this->aysa_persist_local_setup_state_snapshot($setup_user_id, $result['state']['context']['local_setup_state'], $wp_project_id);
        }

        $canonical_project_state_patch = isset($result['canonical_project_state_patch']) && is_array($result['canonical_project_state_patch'])
            ? $result['canonical_project_state_patch']
            : array();
        if ($canonical_project_state_patch !== array() && $wp_project_id > 0) {
            $result['canonical_project_state_persistence'] = $this->aysa_agent8_persist_canonical_project_state_patch($wp_project_id, $canonical_project_state_patch);
        }

        $executed_action_key = isset($payload['action_key']) ? sanitize_key((string) $payload['action_key']) : '';
        if ($wp_project_id > 0 && $this->aysa_adjustments_is_site_audit_bundle_action_key($executed_action_key)) {
            if ($requested_mode === 'action' && isset($result['approval_request']) && is_array($result['approval_request'])) {
                $this->aysa_adjustments_update_site_audit_action_bundle_state($wp_project_id, $executed_action_key, array(
                    'approval_state' => 'suggested',
                    'last_sent_to_agent8_at' => current_time('mysql'),
                ));
            } elseif ($requested_mode === 'approval') {
                if (empty($payload['approved'])) {
                    $this->aysa_adjustments_update_site_audit_action_bundle_state($wp_project_id, $executed_action_key, array(
                        'approval_state' => 'dismissed',
                        'dismissed_at' => current_time('mysql'),
                    ));
                } else {
                    $bundle_changes = array(
                        'approval_state' => 'approved',
                        'approved_at' => current_time('mysql'),
                    );
                    $action_result = isset($result['action_result']) && is_array($result['action_result']) ? $result['action_result'] : array();
                    $action_summary = isset($action_result['summary']) && is_array($action_result['summary']) ? $action_result['summary'] : array();
                    $action_execution = isset($action_result['workspace_snapshot']['execution']) && is_array($action_result['workspace_snapshot']['execution'])
                        ? $action_result['workspace_snapshot']['execution']
                        : array();
                    $prepared_summary = '';
                    if (!empty($action_summary)) {
                        $summary_map = $action_summary;
                        $prepared_summary = sanitize_text_field((string) ($summary_map['next_step'] ?? $summary_map['summary'] ?? $summary_map['bundle'] ?? ''));
                    }
                    if ($prepared_summary === '' && isset($result['summary']) && is_array($result['summary'])) {
                        $summary_map = $result['summary'];
                        $prepared_summary = sanitize_text_field((string) ($summary_map['next_step'] ?? $summary_map['summary'] ?? $summary_map['bundle'] ?? ''));
                    }
                    if (!empty($result['ok']) || !empty($result['action_result']['ok'])) {
                        $bundle_changes['approval_state'] = 'prepared';
                        $bundle_changes['last_prepared_at'] = current_time('mysql');
                        if (!empty($action_summary['execution_state'])) {
                            $bundle_changes['execution_state'] = sanitize_key((string) $action_summary['execution_state']);
                        } elseif (!empty($action_execution['execution_state'])) {
                            $bundle_changes['execution_state'] = sanitize_key((string) $action_execution['execution_state']);
                        }
                    if (!empty($action_summary['execution_package_id'])) {
                        $bundle_changes['execution_package_id'] = sanitize_text_field((string) $action_summary['execution_package_id']);
                    } elseif (!empty($action_execution['execution_package_id'])) {
                        $bundle_changes['execution_package_id'] = sanitize_text_field((string) $action_execution['execution_package_id']);
                    }
                    if ($executed_action_key === 'prepare_metadata_remediation' && isset($action_execution['metadata_execution_package']) && is_array($action_execution['metadata_execution_package'])) {
                        $bundle_changes['metadata_execution_package'] = $action_execution['metadata_execution_package'];
                    }
                    if ($executed_action_key === 'prepare_indexability_review' && isset($action_execution['canonical_execution_package']) && is_array($action_execution['canonical_execution_package'])) {
                        $bundle_changes['canonical_execution_package'] = $action_execution['canonical_execution_package'];
                    }
                    if ($executed_action_key === 'prepare_image_alt_text' && isset($action_execution['image_alt_execution_package']) && is_array($action_execution['image_alt_execution_package'])) {
                        $bundle_changes['image_alt_execution_package'] = $action_execution['image_alt_execution_package'];
                    }
                    if (isset($action_execution['prepare_result']) && is_array($action_execution['prepare_result'])) {
                        $bundle_changes['last_prepare_result'] = $action_execution['prepare_result'];
                    }
                        if ($prepared_summary !== '') {
                            $bundle_changes['last_prepared_summary'] = $prepared_summary;
                        }
                    }
                    $this->aysa_adjustments_update_site_audit_action_bundle_state($wp_project_id, $executed_action_key, $bundle_changes);
                }
            }
        }

        $response_result = array(
            'ok' => true,
            'state' => isset($result['state']) && is_array($result['state']) ? $result['state'] : array(),
            'conversation_id' => (string) ($result['conversation_id'] ?? ''),
            'canonical_project_id' => intval($result['canonical_project_id'] ?? $agent_project_id),
            'wp_project_id' => intval($wp_project_id),
            'phase' => (string) ($result['phase'] ?? 'conversation'),
            'current_step' => (string) ($result['current_step'] ?? 'conversation'),
            'module' => sanitize_key((string) ($result['module'] ?? '')),
            'tab' => sanitize_key((string) ($result['tab'] ?? '')),
            'owner' => 'conversation',
            'capability_level' => 'Conversational',
            'agent_version' => (string) ($result['agent_version'] ?? ''),
            'messages' => isset($result['messages']) && is_array($result['messages'])
                ? $result['messages']
                : $this->aysa_agent8_messages_from_state(isset($result['state']) && is_array($result['state']) ? $result['state'] : array()),
            'assistant_message' => (string) ($result['assistant_message'] ?? ''),
            'suggested_actions' => isset($result['suggested_actions']) && is_array($result['suggested_actions'])
                ? array_values($result['suggested_actions'])
                : array(),
            'persistent_actions' => isset($result['persistent_actions']) && is_array($result['persistent_actions'])
                ? array_values($result['persistent_actions'])
                : array(),
            'approval_request' => isset($result['approval_request']) && is_array($result['approval_request']) ? $result['approval_request'] : null,
            'proposed_action' => isset($result['proposed_action']) && is_array($result['proposed_action']) ? $result['proposed_action'] : null,
            'action_result' => isset($result['action_result']) && is_array($result['action_result']) ? $result['action_result'] : null,
            'billing' => isset($result['billing']) && is_array($result['billing']) ? $result['billing'] : null,
            'needs_approval' => false,
            'form_projection' => null,
            'activity_feed' => isset($result['action_result']['activity_feed']) && is_array($result['action_result']['activity_feed'])
                ? $result['action_result']['activity_feed']
                : array(),
            'canonical_project_state_patch' => array(),
            'canonical_project_state_persistence' => isset($result['canonical_project_state_persistence']) && is_array($result['canonical_project_state_persistence'])
                ? $result['canonical_project_state_persistence']
                : null,
            'product_map' => isset($result['product_map']) && is_array($result['product_map'])
                ? array_values($result['product_map'])
                : array(),
            'timings' => isset($result['timings']) && is_array($result['timings'])
                ? $result['timings']
                : array(),
            'contract_status' => sanitize_key((string) ($result['contract_status'] ?? '')),
            'contract_issue_code' => sanitize_key((string) ($result['contract_issue_code'] ?? '')),
            'contract_issue_details' => isset($result['contract_issue_details']) && is_array($result['contract_issue_details'])
                ? $result['contract_issue_details']
                : array(),
        );

        $local_mutations = isset($result['action_result']['local_mutations']) && is_array($result['action_result']['local_mutations'])
            ? $result['action_result']['local_mutations']
            : array();
        if (!empty($local_mutations)) {
            $response_result['local_mutation_result'] = $this->aysa_agent8_apply_local_mutations($wp_project_id, $local_mutations);
            $response_result = $this->aysa_agent8_refresh_action_result_after_local_mutations($response_result, $wp_project_id);
            if ($this->aysa_agent8_local_mutation_skips_billing($response_result['local_mutation_result'])) {
                $skip_reason = sanitize_key((string) ($response_result['local_mutation_result']['reason'] ?? $response_result['local_mutation_result']['code'] ?? 'already_done'));
                $skip_billing = array(
                    'status' => 'skipped',
                    'estimated_credits_cents' => 0,
                    'actual_credits_cents' => 0,
                    'reason' => $skip_reason,
                    'entries' => array(),
                );
                $result['billing'] = $skip_billing;
                if (isset($result['action_result']) && is_array($result['action_result'])) {
                    $result['action_result']['billing'] = $skip_billing;
                }
                if (isset($response_result['action_result']) && is_array($response_result['action_result'])) {
                    $response_result['action_result']['billing'] = $skip_billing;
                }
            } else {
                $local_billing = $this->aysa_agent8_bill_successful_local_mutation($wp_project_id, $result['action_result'] ?? array(), $response_result['local_mutation_result']);
                if ($local_billing !== array()) {
                    $result['billing'] = $local_billing;
                    if (isset($result['action_result']) && is_array($result['action_result'])) {
                        $result['action_result']['billing'] = $local_billing;
                    }
                    if (isset($response_result['action_result']) && is_array($response_result['action_result'])) {
                        $response_result['action_result']['billing'] = $local_billing;
                    }
                }
            }
        }

        $contract_validation = $this->aysa_agent8_validate_contract_response($response_result);
        if (!empty($contract_validation['status'])) {
            $response_result['contract_status'] = (string) $contract_validation['status'];
        }
        if (!empty($contract_validation['issue_code'])) {
            $response_result['contract_issue_code'] = (string) $contract_validation['issue_code'];
        }
        if (!empty($contract_validation['details']) && is_array($contract_validation['details'])) {
            $response_result['contract_issue_details'] = $contract_validation['details'];
        }
        if (($response_result['contract_status'] ?? '') === 'invalid') {
            $this->aysa_agent8_log_contract_issue($response_result);
            $response_result['suggested_actions'] = array();
            $response_result['approval_request'] = null;
        }

        $local_persistence_ms = (microtime(true) - $local_persistence_started_at) * 1000;
        $response_result = $this->aysa_agent8_attach_plugin_timings(
            $response_result,
            $execution_path,
            $bundle_build_ms,
            $broker_request_ms,
            $local_persistence_ms,
            $request_started_at,
            $payload_size_bytes
        );
        $response_result = $this->aysa_agent8_bridge_compact_payload_if_needed($response_result, array(
            'task' => 'agent8',
            'run_id' => '',
            'project_id' => $wp_project_id,
            'conversation_id' => (string) ($response_result['conversation_id'] ?? ''),
            'source' => 'execute_agent8_response',
        ));
        $this->aysa_agent8_save_session($wp_user_id, $site_host, $conversation_language, $response_result, $request_epoch);

        $resolved_wallet_project_id = intval($result['canonical_project_id'] ?? $agent_project_id);
        $resolved_binding_valid = !empty($has_valid_access_key)
            || !empty($result['action_result']['activation_validation']['valid'])
            || $resolved_wallet_project_id > 0;
        if ($resolved_binding_valid) {
            $wallet_snapshot = $this->aysa_build_agent_wallet_snapshot($resolved_wallet_project_id > 0 ? $resolved_wallet_project_id : $agent_project_id);
            $wallet_snapshot = $this->aysa_agent8_apply_billing_usage_hint($wallet_snapshot, isset($result['action_result']) && is_array($result['action_result']) ? $result['action_result'] : array());
            $wallet_snapshot = $this->aysa_agent8_apply_top_level_billing_usage_hint($wallet_snapshot, $result);
        } else {
            $wallet_snapshot = array();
        }

        return $this->aysa_agent8_bridge_compact_payload_if_needed(array(
            'result' => $response_result,
            'wallet_snapshot' => $wallet_snapshot,
        ), array(
            'task' => 'agent8',
            'run_id' => '',
            'project_id' => $wp_project_id,
            'conversation_id' => (string) ($response_result['conversation_id'] ?? ''),
            'source' => 'execute_agent8_return',
        ));
    }

    private function aysa_execute_agent9_conversation($input) {
        $mode = isset($input['mode']) ? sanitize_key(wp_unslash((string) $input['mode'])) : 'bootstrap';
        $conversation_language = $this->aysa_agent8_normalize_conversation_language(
            isset($input['conversation_language']) ? wp_unslash((string) $input['conversation_language']) : '',
            'en'
        );

        $wp_project_id = intval(get_option('aysa_project_id', 0));
        $posted_project_id = isset($input['project_id']) ? intval(wp_unslash((string) $input['project_id'])) : 0;
        $site_host = isset($input['site_host']) ? sanitize_text_field(wp_unslash((string) $input['site_host'])) : '';
        $user_message = isset($input['user_message']) ? sanitize_textarea_field(wp_unslash((string) $input['user_message'])) : '';
        $wp_user_id = intval($input['wp_user_id'] ?? get_current_user_id());

        $auth_manager = class_exists('Aysa_Auth_Manager') ? Aysa_Auth_Manager::get_instance() : null;
        $auth_status = is_object($auth_manager) && method_exists($auth_manager, 'is_authenticated')
            ? $auth_manager->is_authenticated()
            : array();
        $setup_user_id = isset($auth_status['aysa_user_id']) ? intval($auth_status['aysa_user_id']) : 0;
        if ($setup_user_id <= 0) {
            $setup_user_id = intval(get_option('aysa_user_id', 0));
        }
        if ($setup_user_id <= 0) {
            $setup_user_id = intval($this->get_user_id());
        }
        if ($setup_user_id <= 0) {
            $setup_user_id = intval($this->aysa_get_authenticated_external_user_id());
        }

        $saved_session = $this->aysa_agent9_load_session($wp_user_id, $site_host, $conversation_language);
        if ($mode === 'reset') {
            $this->aysa_agent9_clear_session($wp_user_id, $site_host);
            $saved_session = array();
            $mode = 'bootstrap';
            $input['conversation_id'] = '';
        }

        $website = $this->aysa_setup_normalize_url(home_url('/'));
        $agent_project_id = $posted_project_id > 0 ? $posted_project_id : $wp_project_id;
        $payload = array(
            'project_id' => $agent_project_id,
            'wp_project_id' => $wp_project_id,
            'conversation_language' => $conversation_language,
            'site_host' => $site_host,
            'website' => $website,
            'wp_user_id' => $wp_user_id,
            'aysa_user_id' => $setup_user_id,
            'migration_mode' => true,
        );

        $saved_conversation_id = isset($saved_session['conversation_id']) ? trim((string) $saved_session['conversation_id']) : '';
        $posted_conversation_id = isset($input['conversation_id']) ? sanitize_text_field(wp_unslash((string) $input['conversation_id'])) : '';
        $conversation_id = $posted_conversation_id !== '' ? $posted_conversation_id : $saved_conversation_id;
        if ($conversation_id !== '') {
            $payload['conversation_id'] = $conversation_id;
        }

        if ($setup_user_id > 0) {
            $payload['local_setup_state'] = $this->aysa_get_local_setup_state_for_user($setup_user_id, $wp_project_id);
        }

        if ($mode === 'message') {
            $payload['user_message'] = $user_message;
        } elseif ($mode === 'action' || $mode === 'approval') {
            $payload['action_key'] = isset($input['action_key']) ? sanitize_key(wp_unslash((string) $input['action_key'])) : '';
            if ($mode === 'approval') {
                $payload['approved'] = !empty($input['approved']);
            }
            if (isset($input['action_payload'])) {
                $raw_action_payload = wp_unslash($input['action_payload']);
                if (is_string($raw_action_payload) && $raw_action_payload !== '') {
                    $decoded_action_payload = json_decode($raw_action_payload, true);
                    if (is_array($decoded_action_payload)) {
                        $payload['action_payload'] = $decoded_action_payload;
                    }
                } elseif (is_array($raw_action_payload)) {
                    $payload['action_payload'] = $raw_action_payload;
                }
            }
        }

        $endpoint = match ($mode) {
            'bootstrap' => '/v1/agent9/bootstrap',
            'approval' => '/v1/agent9/approval',
            'action' => '/v1/agent9/action',
            default => '/v1/agent9/message',
        };
        $result = $this->aysa_broker_request('POST', $endpoint, $payload, 45);
        if (is_wp_error($result)) {
            return $result;
        }
        if (!is_array($result) || empty($result['ok'])) {
            return new WP_Error('agent9_request_failed', isset($result['error']) ? (string) $result['error'] : 'Agent 9 request failed.');
        }

        $this->aysa_agent9_save_session($wp_user_id, $site_host, $conversation_language, $result);

        return array(
            'result' => $result,
            'wallet_snapshot' => array(),
        );
    }

    private function aysa_agent8_attach_plugin_timings($response_result, $execution_path, $bundle_build_ms, $broker_request_ms, $local_persistence_ms, $request_started_at, $payload_size_bytes = 0) {
        if (!is_array($response_result)) {
            $response_result = array();
        }

        $existing_timings = isset($response_result['timings']) && is_array($response_result['timings'])
            ? $response_result['timings']
            : array();
        $existing_timings['plugin'] = array(
            'bundle_build_ms' => round(max(0, (float) $bundle_build_ms), 2),
            'broker_request_ms' => round(max(0, (float) $broker_request_ms), 2),
            'local_persistence_ms' => round(max(0, (float) $local_persistence_ms), 2),
            'payload_size_bytes' => max(0, intval($payload_size_bytes)),
            'total_plugin_ms' => round(max(0, (microtime(true) - (float) $request_started_at) * 1000), 2),
        );
        $response_result['timings'] = $existing_timings;
        $response_result['execution_path'] = in_array((string) $execution_path, array('sync', 'async'), true)
            ? (string) $execution_path
            : 'async';

        return $response_result;
    }

    public function display_monitoring_analytics_page() {
        if (!$this->check_authentication()) {
            $aysa_section_title = 'Monitoring & Analytics';
            include AYSA_PLUGIN_DIR . 'admin/partials/aysa-login-required.php';
            return;
        }
        include_once AYSA_PLUGIN_DIR . 'admin/partials/aysa-monitoring-analytics.php';
    }
    
    public function display_agent8_page() {
        $this->display_agent8_v3_lab_page();
    }

    public function display_agent9_page() {
        $this->display_agent8_v3_lab_page();
    }

    public function display_agent8_v3_lab_page() {
        if (!$this->aysa_current_user_can_workspace()) {
            $aysa_section_title = 'AYSA Agent 8';
            include AYSA_PLUGIN_DIR . 'admin/partials/aysa-login-required.php';
            return;
        }

        include_once AYSA_PLUGIN_DIR . 'admin/partials/aysa-agent8-v3-lab.php';
    }

    public function display_agent8_beta_issues_page() {
        if (!$this->aysa_agent8v3_qa_enabled()) {
            wp_die(esc_html__('Agent8 QA is disabled.', 'aysaaiseo'));
        }
        if (!$this->aysa_current_user_can_workspace()) {
            $aysa_section_title = 'Agent8 Beta Issues';
            include AYSA_PLUGIN_DIR . 'admin/partials/aysa-login-required.php';
            return;
        }
        ?>
        <div class="wrap aysa-agent8v3-beta-issues-admin">
            <h1>Agent8 Beta Issues</h1>
            <p>Rapoarte client-safe trimise din Agent8 V3. Nu conțin chei, tokenuri, cookies, parole, prompturi brute sau conținut complet de pagină.</p>
            <p>
                <button type="button" class="button button-primary" id="aysa-agent8v3-beta-issues-refresh">Reîncarcă rapoartele</button>
                <a class="button" href="<?php echo esc_url(admin_url('admin.php?page=aysa-agent8-v3-lab')); ?>">Deschide Agent8 V3</a>
            </p>
            <div id="aysa-agent8v3-beta-issues-admin-list" class="aysa-agent8v3-beta-issues-admin-list">
                <p>Se încarcă rapoartele...</p>
            </div>
        </div>
        <script>
        jQuery(function($) {
            const cfg = window.aysaAgent8V3Lab || {};
            function esc(value) {
                return String(value || '').replace(/[&<>"']/g, function(ch) {
                    return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'})[ch];
                });
            }
            function loadIssues() {
                $('#aysa-agent8v3-beta-issues-admin-list').html('<p>Se încarcă rapoartele...</p>');
                $.post(cfg.ajax_url || ajaxurl, {
                    action: 'aysa_agent8v3_beta_issues_list',
                    nonce: cfg.nonce || '<?php echo esc_js(wp_create_nonce('aysa_nonce')); ?>',
                    limit: 50
                }).done(function(response) {
                    const issues = response && response.success && response.data && response.data.issues ? response.data.issues : [];
                    if (!issues.length) {
                        $('#aysa-agent8v3-beta-issues-admin-list').html('<p>Nu există rapoarte beta încă.</p>');
                        return;
                    }
                    $('#aysa-agent8v3-beta-issues-admin-list').html(issues.map(function(issue) {
                        return '<article class="aysa-agent8v3-beta-issue-row">' +
                            '<h2>' + esc(issue.issue_id) + ' <span>' + esc(issue.severity) + '</span> <em>' + esc(issue.status) + '</em></h2>' +
                            '<p><strong>Site:</strong> ' + esc(issue.site_host) + ' · <strong>Modul:</strong> ' + esc(issue.module_key) + ' · <strong>Fază:</strong> ' + esc(issue.phase) + '</p>' +
                            '<p>' + esc(issue.human_summary) + '</p>' +
                            '<p><label>Status <select class="aysa-agent8v3-beta-issue-status" data-issue-id="' + esc(issue.issue_id) + '">' +
                            ['new','triaged','sent_to_codex','fixed_source','deployed','verified_runtime','closed','rejected_duplicate'].map(function(status) {
                                return '<option value="' + esc(status) + '"' + (status === issue.status ? ' selected' : '') + '>' + esc(status) + '</option>';
                            }).join('') +
                            '</select></label> <button type="button" class="button aysa-agent8v3-beta-issue-status-save" data-issue-id="' + esc(issue.issue_id) + '">Salvează status</button></p>' +
                            '<p><button type="button" class="button aysa-agent8v3-beta-issue-copy-codex">Copy Codex report</button></p>' +
                            '<textarea readonly rows="9">' + esc(issue.codex_bundle_markdown) + '</textarea>' +
                        '</article>';
                    }).join(''));
                }).fail(function() {
                    $('#aysa-agent8v3-beta-issues-admin-list').html('<p>Nu am putut încărca rapoartele.</p>');
                });
            }
            $('#aysa-agent8v3-beta-issues-refresh').on('click', loadIssues);
            $(document).on('click', '.aysa-agent8v3-beta-issue-status-save', function() {
                const issueId = String($(this).attr('data-issue-id') || '');
                const status = $('.aysa-agent8v3-beta-issue-status[data-issue-id="' + issueId + '"]').val() || 'triaged';
                $.post(cfg.ajax_url || ajaxurl, {
                    action: 'aysa_agent8v3_beta_issue_status',
                    nonce: cfg.nonce || '<?php echo esc_js(wp_create_nonce('aysa_nonce')); ?>',
                    issue_id: issueId,
                    status: status
                }).done(loadIssues);
            });
            $(document).on('click', '.aysa-agent8v3-beta-issue-copy-codex', function() {
                const textarea = $(this).closest('.aysa-agent8v3-beta-issue-row').find('textarea').get(0);
                if (!textarea) {
                    return;
                }
                textarea.select();
                document.execCommand('copy');
                $(this).text('Copiat');
            });
            loadIssues();
        });
        </script>
        <?php
    }

    public function display_legacy_page() {
        if (!$this->check_authentication()) {
            $aysa_section_title = 'Legacy';
            include AYSA_PLUGIN_DIR . 'admin/partials/aysa-login-required.php';
            return;
        }

        $links = [
            ['label' => 'Setup SEO Profile', 'url' => admin_url('admin.php?page=aysa-setup-seo-profile')],
            ['label' => 'Dashboard', 'url' => admin_url('admin.php?page=aysa')],
            ['label' => 'Research & Analysis', 'url' => admin_url('admin.php?page=aysa-research')],
            ['label' => 'Technical Optimization', 'url' => admin_url('admin.php?page=aysa-technical-optimization')],
            ['label' => 'On-Page Optimization', 'url' => admin_url('admin.php?page=aysa-on-page-optimization')],
            ['label' => 'Off-Page Optimization', 'url' => admin_url('admin.php?page=aysa-off-page-optimization')],
            ['label' => 'Monitoring & Analytics', 'url' => admin_url('admin.php?page=aysa-monitoring-analytics')],
            ['label' => 'Settings', 'url' => admin_url('admin.php?page=aysa-settings')],
            ['label' => 'Welcome to aysa', 'url' => admin_url('admin.php?page=aysa-welcome')],
        ];
        ?>
        <div class="wrap">
            <h1>Legacy</h1>
            <p>All existing AYSA plugin areas remain available here while the main product surface moves into AYSA AI Agent.</p>
            <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;max-width:1100px;margin-top:20px;">
                <?php foreach ($links as $link): ?>
                    <a href="<?php echo esc_url($link['url']); ?>" style="display:block;padding:16px 18px;background:#fff;border:1px solid #dcdcde;border-radius:10px;text-decoration:none;color:#1d2327;font-weight:600;">
                        <?php echo esc_html($link['label']); ?>
                    </a>
                <?php endforeach; ?>
            </div>
        </div>
        <?php
    }
    
    public function display_setup_seo_profile_page() {
        if (!$this->check_authentication()) {
            $aysa_section_title = 'Setup SEO Profile';
            include AYSA_PLUGIN_DIR . 'admin/partials/aysa-login-required.php';
            return;
        }
        $setup_completion_reason = array();
        $setup_completed_effectively = $this->aysa_is_setup_profile_completed_effectively($setup_completion_reason);
        if ($this->aysa_is_apprentice_mode_enabled() && $setup_completed_effectively) {
            $this->display_agent8_v3_lab_page();
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed callback inventory: this read-only/core lifecycle request is authenticated by WordPress or an enclosing guard and performs no unapproved write.
        $force_edit_setup = !empty($_GET['edit_setup']) ? true : false;
        if ($setup_completed_effectively && !$force_edit_setup) {
            wp_safe_redirect($this->aysa_last_research_tab_url());
            exit;
        }
        include_once AYSA_PLUGIN_DIR . 'admin/partials/aysa-setup-wizard.php';
    }
    
    public function display_omnichannel_seo_page() {
        if (!$this->check_authentication()) {
            $aysa_section_title = 'Omnichannel SEO';
            include AYSA_PLUGIN_DIR . 'admin/partials/aysa-login-required.php';
            return;
        }
        include_once AYSA_PLUGIN_DIR . 'admin/partials/aysa-omnichannel-seo.php';
    }
    
    public function display_settings_page() {
        if (!$this->check_authentication()) {
            $aysa_section_title = 'Settings';
            include AYSA_PLUGIN_DIR . 'admin/partials/aysa-login-required.php';
            return;
        }
        include_once AYSA_PLUGIN_DIR . 'admin/partials/aysa-settings.php';
    }

    public function display_activation_page() {
        if (!$this->aysa_current_user_can_workspace()) {
            wp_die(esc_html__('Access denied. You do not have permission to access AYSA.', 'aysaaiseo'));
        }
        $nonce = wp_create_nonce('aysa_nonce');
        $logo_url = 'https://aysa.ai/assets/aysa-ai-logo.png';
        $activation_language = 'en';
        $activation_copy = array(
            'ro' => array(
                'title' => 'AYSA AI SEO',
                'subtitle' => 'Activare plugin',
                'language' => 'Limbă',
                'intro' => 'Salut si multumim pentru interes. Ca sa putem continua, am nevoie de cheia secreta asociata website-ului curent.',
                'instructions' => 'Gasesti cheia in <a href="https://app.aysa.ai/websites" target="_blank" rel="noopener noreferrer">app.aysa.ai/websites</a>, in pagina website-ului asociat acestui domeniu. Cauta Secret Key, apoi da copy si lipeste cheia aici in chat.',
                'reminder' => 'Pana cand cheia este valida, ramanem la acest pas.',
                'placeholder' => 'Lipește aici cheia secretă...',
                'send' => 'Trimite cheia',
                'hint' => 'Nu consumam credite pana cand cheia nu este validata.',
                'where_title' => 'De unde iei cheia',
                'where_body' => 'Deschide <a href="https://app.aysa.ai/websites" target="_blank" rel="noopener noreferrer">app.aysa.ai/websites</a>, alege website-ul curent si copiaza Secret Key din pagina lui.',
                'continue_title' => 'Ce facem acum',
                'continue_body' => 'Lipeste cheia secreta in chat. Daca nu este corecta, iti voi cere din nou cheia corecta.',
                'offer_intro' => 'AYSA te ajuta sa pornesti SEO mai simplu: conectezi website-ul, activezi pluginul, apoi agentul te ghideaza si executa pasii importanti pentru SEO.',
                'offer_pricing' => 'Planurile pornesc de la Starter €29/luna, apoi Growth €69/luna si Scale €99/luna. Ai si o perioada de trial de 14 zile, cu €0 today.',
                'offer_benefit' => 'Pluginul te ajuta cu setup, conectare, monitorizare, prioritizarea paginilor importante si urmatorii pasi SEO, fara sa trebuiasca sa cauti singur prin toate sectiunile.',
                'offer_cta' => 'Cand esti gata, lipeste cheia secreta aici in chat ca sa poti incepe sa-l folosesti.',
                'empty' => 'Am nevoie de cheia secreta ca sa pot continua. Te rog copiaza Secret Key din app.aysa.ai si lipeste-o aici.',
                'invalid_prefix' => 'Cheia nu a fost validata.',
                'invalid_suffix' => 'Te rog verifica Secret Key-ul din app.aysa.ai, din zona Websites a website-ului curent, apoi lipeste-l din nou aici.',
                'success' => 'Cheia a fost validata. Multumim. Activez pluginul si continuam automat.',
                'redirect' => 'Redirectionez catre AYSA AI SEO...',
            ),
            'bg' => array(
                'title' => 'AYSA AI SEO',
                'subtitle' => 'Активиране на плъгина',
                'language' => 'Език',
                'intro' => 'Здравей и благодарим за интереса. За да продължим, ми трябва секретният ключ за текущия сайт.',
                'instructions' => 'Ще намериш ключа в <a href="https://app.aysa.ai/websites" target="_blank" rel="noopener noreferrer">app.aysa.ai/websites</a>, в страницата на сайта за този домейн. Намери Secret Key, копирай го и го постави тук в чата.',
                'reminder' => 'Докато ключът не е валиден, оставаме на тази стъпка.',
                'placeholder' => 'Постави секретния ключ тук...',
                'send' => 'Изпрати ключа',
                'hint' => 'Не се използват кредити, докато ключът не бъде валидиран.',
                'where_title' => 'Откъде да вземеш ключа',
                'where_body' => 'Отвори <a href="https://app.aysa.ai/websites" target="_blank" rel="noopener noreferrer">app.aysa.ai/websites</a>, избери текущия сайт и копирай Secret Key от страницата му.',
                'continue_title' => 'Какво следва',
                'continue_body' => 'Постави секретния ключ в чата. Ако не е правилен, ще поискам правилния ключ отново.',
                'offer_intro' => 'AYSA ти помага да започнеш SEO по-лесно: свързваш сайта, активираш плъгина, а после агентът те води и изпълнява важните SEO стъпки.',
                'offer_pricing' => 'Плановете започват от Starter €29/месец, след това Growth €69/месец и Scale €99/месец. Имаш и 14-дневен trial с €0 today.',
                'offer_benefit' => 'Плъгинът помага със setup, свързване, мониторинг, приоритизиране на важните страници и следващите SEO стъпки, без да търсиш сам през всички секции.',
                'offer_cta' => 'Когато си готов, постави секретния ключ тук в чата, за да започнеш да го използваш.',
                'empty' => 'Нужен ми е секретният ключ, за да продължа. Моля, копирай Secret Key от app.aysa.ai и го постави тук.',
                'invalid_prefix' => 'Ключът не беше валидиран.',
                'invalid_suffix' => 'Моля, провери Secret Key в app.aysa.ai, в секцията Websites за текущия сайт, и го постави отново тук.',
                'success' => 'Ключът е валидиран. Благодаря. Активирам плъгина и продължаваме автоматично.',
                'redirect' => 'Пренасочвам към AYSA AI SEO...',
            ),
            'en' => array(
                'title' => 'AYSA AI SEO',
                'subtitle' => 'Plugin activation',
                'intro' => 'Hello and thank you for your interest. To continue, I need the secret key associated with the current website.',
                'instructions' => 'You can find it in <a href="https://app.aysa.ai/websites" target="_blank" rel="noopener noreferrer">app.aysa.ai/websites</a>, inside the page for the website linked to this domain. Look for Secret Key, copy it, and paste it here in the chat.',
                'reminder' => 'Until the key is valid, we stay on this step.',
                'placeholder' => 'Paste the secret key here...',
                'send' => 'Send key',
                'hint' => 'No credits are consumed until the key is validated.',
                'language' => 'Language',
                'where_title' => 'Where to get the key',
                'where_body' => 'Open <a href="https://app.aysa.ai/websites" target="_blank" rel="noopener noreferrer">app.aysa.ai/websites</a>, select the current website, and copy the Secret Key from its page.',
                'continue_title' => 'What happens next',
                'continue_body' => 'Paste the secret key in the chat. If it is not correct, I will ask for the correct key again.',
                'offer_intro' => 'AYSA helps you start SEO in a simpler way: you connect the website, activate the plugin, and then the agent guides you and executes the important SEO steps.',
                'offer_pricing' => 'Plans start with Starter at €29/month, then Growth at €69/month and Scale at €99/month. You also get a 14-day trial with €0 today.',
                'offer_benefit' => 'The plugin helps with setup, connection, monitoring, prioritizing important pages, and the next SEO steps, without forcing you to search through every section yourself.',
                'offer_cta' => 'When you are ready, paste the secret key here in the chat so you can start using it.',
                'empty' => 'I need the secret key before I can continue. Please copy the Secret Key from app.aysa.ai and paste it here.',
                'invalid_prefix' => 'The key was not validated.',
                'invalid_suffix' => 'Please check the Secret Key in app.aysa.ai, in the Websites area for the current website, and paste it here again.',
                'success' => 'The key has been validated. Thank you. I am activating the plugin and we will continue automatically.',
                'redirect' => 'Redirecting to AYSA AI SEO...',
            ),
        );
        ?>
        <div class="wrap aysa-agent8v3-lab aysa-activation-agent-wrap">
            <style>
                .aysa-activation-agent-wrap{max-width:none;margin:18px 24px 0 0}
                .aysa-activation-agent-shell{width:min(1480px,calc(100vw - 200px));background:#fff;border:1px solid #e2e8f0;border-radius:32px;padding:20px 24px 24px;box-shadow:none}
                .aysa-activation-brand{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:12px}
                .aysa-activation-brand img{display:block;height:42px;width:auto}
                .aysa-activation-brand-meta{display:flex;flex-direction:column;gap:4px}
                .aysa-activation-brand-meta .brand-kicker{font-size:12px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#1faa4b}
                .aysa-activation-agent-head{display:flex;justify-content:space-between;align-items:flex-start;gap:20px;margin-bottom:14px}
                .aysa-activation-agent-head h1{margin:0 0 6px;font-size:28px;line-height:1.05}
                .aysa-activation-agent-head p{margin:0;color:#5b6b84;font-size:15px}
                .aysa-activation-agent-toolbar{display:flex;align-items:center;gap:14px}
                .aysa-activation-agent-pill{display:inline-flex;align-items:center;border-radius:999px;padding:10px 16px;background:#eefbf2;border:1px solid #b6e6c2;color:#15803d;font-weight:700}
                .aysa-activation-language{display:flex;align-items:center;gap:8px;color:#475569;font-size:14px;font-weight:600}
                .aysa-activation-language select{border:1px solid #cbd5e1;border-radius:12px;padding:8px 12px;font-size:14px;background:#fff}
                .aysa-activation-agent-grid{display:grid;grid-template-columns:minmax(0,2.5fr) minmax(300px,.85fr);gap:22px;align-items:start}
                .aysa-activation-chat{border:1px solid #e7edf5;border-radius:26px;background:#fbfdff;display:flex;flex-direction:column;overflow:hidden}
                .aysa-activation-chat-log{display:flex;flex-direction:column;gap:12px;flex:1;min-height:180px;max-height:280px;overflow-y:auto;padding:22px 22px 10px}
                .aysa-activation-bubble{max-width:82%;padding:16px 18px;border-radius:18px;font-size:15px;line-height:1.65;white-space:pre-wrap}
                .aysa-activation-bubble.assistant{align-self:flex-start;background:#f4f7fb;border:1px solid #dde6f3;color:#14233b}
                .aysa-activation-bubble.user{align-self:flex-end;background:#1d4ed8;color:#fff}
                .aysa-activation-bubble.system-ok{align-self:flex-start;background:#effaf1;border:1px solid #b7e2c1;color:#166534}
                .aysa-activation-bubble.typing{display:inline-flex;align-items:center;gap:7px;background:#f4f7fb;border:1px solid #dde6f3;color:#64748b}
                .aysa-activation-bubble.typing span{display:block;width:8px;height:8px;border-radius:999px;background:#9fb0c8;animation:aysaActivationPulse 1.1s infinite ease-in-out}
                .aysa-activation-bubble.typing span:nth-child(2){animation-delay:.15s}
                .aysa-activation-bubble.typing span:nth-child(3){animation-delay:.3s}
                .aysa-activation-composer-wrap{padding:16px 22px 18px;border-top:1px solid #e7edf5;background:#fff;position:relative}
                .aysa-activation-composer{display:flex;gap:12px}
                .aysa-activation-composer textarea{flex:1;min-height:78px;border-radius:20px;border:1px solid #cbd5e1;padding:15px 17px;font-size:15px;line-height:1.55;resize:vertical;box-shadow:0 0 0 0 rgba(31,170,75,0)}
                .aysa-activation-composer-wrap.is-attention textarea{border-color:#1faa4b;box-shadow:0 0 0 4px rgba(31,170,75,.12);animation:aysaActivationGlow 1.5s ease-in-out infinite}
                .aysa-activation-composer button{align-self:flex-end;border:0;border-radius:16px;background:#1faa4b;color:#fff;padding:14px 20px;font-weight:700;cursor:pointer}
                .aysa-activation-composer button[disabled]{opacity:.6;cursor:default}
                .aysa-activation-note{margin-top:10px;color:#64748b;font-size:13px}
                .aysa-activation-sidecard{background:#fff;border:1px solid #e2e8f0;border-radius:24px;padding:24px}
                .aysa-activation-sidecard h3{margin:0 0 10px;font-size:18px}
                .aysa-activation-sidecard p{margin:0;color:#516277;line-height:1.65}
                .aysa-activation-sidecard + .aysa-activation-sidecard{margin-top:18px}
                @keyframes aysaActivationPulse{0%,80%,100%{transform:scale(.8);opacity:.45}40%{transform:scale(1);opacity:1}}
                @keyframes aysaActivationGlow{0%,100%{box-shadow:0 0 0 0 rgba(31,170,75,.12)}50%{box-shadow:0 0 0 6px rgba(31,170,75,.18)}}
                @media (max-width: 1280px){
                    .aysa-activation-agent-shell{width:calc(100vw - 190px)}
                    .aysa-activation-agent-grid{grid-template-columns:1fr}
                    .aysa-activation-chat-log{min-height:180px;max-height:260px}
                }
                @media (max-width: 960px){
                    .aysa-activation-agent-wrap{margin-right:12px}
                    .aysa-activation-agent-grid{grid-template-columns:1fr}
                    .aysa-activation-agent-shell{width:100%;padding:22px}
                    .aysa-activation-agent-head{flex-direction:column}
                    .aysa-activation-brand{margin-bottom:14px}
                    .aysa-activation-bubble{max-width:100%}
                    .aysa-activation-chat-log{min-height:160px;max-height:240px;padding:18px 18px 10px}
                    .aysa-activation-composer-wrap{padding:18px 22px 20px}
                }
            </style>
            <div class="aysa-activation-agent-shell">
                <div class="aysa-activation-brand">
                    <img src="<?php echo esc_url($logo_url); ?>" alt="AYSA AI SEO">
                    <div class="aysa-activation-brand-meta">
                        <span class="brand-kicker">AI SEO Workspace</span>
                    </div>
                </div>
                <div class="aysa-activation-agent-head">
                    <div>
                        <h1><?php echo esc_html($activation_copy[$activation_language]['title']); ?></h1>
                        <p><?php echo esc_html($activation_copy[$activation_language]['subtitle']); ?></p>
                    </div>
                    <div class="aysa-activation-agent-toolbar">
                        <label class="aysa-activation-language" for="aysa-activation-language">
                            <span id="aysa-activation-language-label"><?php echo esc_html($activation_copy[$activation_language]['language']); ?></span>
                            <select id="aysa-activation-language">
                                <option value="en" selected>English</option>
                                <option value="ro">Romanian</option>
                                <option value="bg">Bulgarian</option>
                            </select>
                        </label>
                        <span class="aysa-activation-agent-pill" id="aysa-activation-hint"><?php echo esc_html($activation_copy[$activation_language]['hint']); ?></span>
                    </div>
                </div>
                <div class="aysa-activation-agent-grid">
                    <section class="aysa-activation-chat">
                        <div class="aysa-activation-chat-log" id="aysa-activation-chat-log"></div>
                        <div class="aysa-activation-composer-wrap">
                            <form class="aysa-activation-composer" id="aysa-activation-chat-form">
                                <textarea id="aysa-activation-chat-input" autocomplete="off" placeholder="<?php echo esc_attr($activation_copy[$activation_language]['placeholder']); ?>"></textarea>
                                <button type="submit" id="aysa-activation-chat-send"><?php echo esc_html($activation_copy[$activation_language]['send']); ?></button>
                            </form>
                            <div class="aysa-activation-note" id="aysa-activation-status"><?php echo esc_html($activation_copy[$activation_language]['reminder']); ?></div>
                        </div>
                    </section>
                    <aside>
                        <div class="aysa-activation-sidecard" id="aysa-activation-sidecard-where">
                            <h3><?php echo esc_html($activation_copy[$activation_language]['where_title']); ?></h3>
                            <p><?php echo esc_html($activation_copy[$activation_language]['where_body']); ?></p>
                        </div>
                        <div class="aysa-activation-sidecard" id="aysa-activation-sidecard-next">
                            <h3><?php echo esc_html($activation_copy[$activation_language]['continue_title']); ?></h3>
                            <p><?php echo esc_html($activation_copy[$activation_language]['continue_body']); ?></p>
                        </div>
                    </aside>
                </div>
            </div>
        </div>
        <script>
        jQuery(function($){
            const ajaxUrl = <?php echo wp_json_encode(admin_url('admin-ajax.php')); ?>;
            const nonce = <?php echo wp_json_encode($nonce); ?>;
            const host = window.location.hostname || '';
            const copy = <?php echo wp_json_encode($activation_copy); ?>;
            const $log = $('#aysa-activation-chat-log');
            const $input = $('#aysa-activation-chat-input');
            const $send = $('#aysa-activation-chat-send');
            const $status = $('#aysa-activation-status');
            const $language = $('#aysa-activation-language');
            const $languageLabel = $('#aysa-activation-language-label');
            const $hint = $('#aysa-activation-hint');
            const $whereCard = $('#aysa-activation-sidecard-where');
            const $nextCard = $('#aysa-activation-sidecard-next');
            const $composerWrap = $('.aysa-activation-composer-wrap');
            let currentLanguage = 'en';
            let introTimeouts = [];
            let hasExplainedOffer = false;
            let activationRunId = '';
            let activationPollTimer = null;

            function escapeHtml(text) {
                return $('<div>').text(text || '').html();
            }

            function t() {
                return copy[currentLanguage] || copy.en;
            }

            function appendBubble(role, text) {
                const cls = role === 'user' ? 'user' : (role === 'system-ok' ? 'system-ok' : 'assistant');
                $log.append('<div class="aysa-activation-bubble ' + cls + '">' + escapeHtml(text) + '</div>');
                $log.scrollTop($log.prop('scrollHeight'));
            }

            function appendBubbleHtml(role, html) {
                const cls = role === 'user' ? 'user' : (role === 'system-ok' ? 'system-ok' : 'assistant');
                $log.append('<div class="aysa-activation-bubble ' + cls + '">' + html + '</div>');
                $log.scrollTop($log.prop('scrollHeight'));
            }

            function appendTypingBubble() {
                $log.append('<div class="aysa-activation-bubble typing" id="aysa-activation-typing"><span></span><span></span><span></span></div>');
                $log.scrollTop($log.prop('scrollHeight'));
            }

            function clearIntroTimers() {
                introTimeouts.forEach(function(id){ clearTimeout(id); });
                introTimeouts = [];
                $('#aysa-activation-typing').remove();
            }

            function appendIntro() {
                clearIntroTimers();
                appendTypingBubble();
                introTimeouts.push(setTimeout(function(){
                    $('#aysa-activation-typing').remove();
                    appendBubble('assistant', t().intro);
                    $composerWrap.addClass('is-attention');
                    $input.trigger('focus');
                }, 650));
                introTimeouts.push(setTimeout(function(){
                    appendBubbleHtml('assistant', t().instructions);
                }, 1150));
            }

            function renderStaticCopy() {
                document.title = t().title + ' ‹ <?php echo esc_js(get_bloginfo('name')); ?> — WordPress';
                $('.aysa-activation-agent-head h1').text(t().title);
                $('.aysa-activation-agent-head p').text(t().subtitle);
                $languageLabel.text(t().language || 'Language');
                $hint.text(t().hint);
                $input.attr('placeholder', t().placeholder);
                $send.text(t().send);
                $status.text(t().reminder);
                $whereCard.find('h3').text(t().where_title);
                $whereCard.find('p').html(t().where_body);
                $nextCard.find('h3').text(t().continue_title);
                $nextCard.find('p').text(t().continue_body);
            }

            function setBusy(isBusy) {
                $send.prop('disabled', isBusy);
                $input.prop('disabled', isBusy);
            }

            function stopActivationPolling() {
                if (activationPollTimer) {
                    clearTimeout(activationPollTimer);
                    activationPollTimer = null;
                }
            }

            function handleActivationConversationResult(result) {
                const payload = result && result.result ? result.result : result;
                if (!payload || typeof payload !== 'object') {
                    appendBubble('assistant', t().invalid_prefix + ' ' + t().invalid_suffix);
                    $status.text(t().reminder);
                    $composerWrap.addClass('is-attention');
                    return;
                }
                const assistantMessage = String(payload.assistant_message || '').trim();
                if (assistantMessage) {
                    if (payload.activation_verified) {
                        appendBubble('system-ok', '✓ ' + assistantMessage);
                    } else {
                        appendBubble('assistant', assistantMessage);
                    }
                }
                if (payload.activation_verified) {
                    $status.text(t().redirect);
                    setTimeout(function(){
                        window.location.href = payload.redirect_url || <?php echo wp_json_encode(admin_url('admin.php?page=aysa')); ?>;
                    }, 900);
                    return;
                }
                $status.text(t().reminder);
                $composerWrap.addClass('is-attention');
            }

            function pollActivationConversationStatus(userMessage) {
                stopActivationPolling();
                activationPollTimer = setTimeout(function tick() {
                    $.ajax({
                        url: ajaxUrl,
                        type: 'POST',
                        dataType: 'json',
                        data: {
                            action: 'aysa_conversation_async_status',
                            task: 'activation',
                            nonce: nonce,
                            site_host: host,
                            conversation_language: currentLanguage,
                            user_message: userMessage || '',
                            run_id: activationRunId
                        },
                        success: function(resp) {
                            const data = resp && resp.success && resp.data ? resp.data : null;
                            const state = data && data.status ? String(data.status) : 'failed';
                            if (state === 'queued' || state === 'running') {
                                $status.text(state === 'queued' ? 'Queued...' : 'Running...');
                                activationPollTimer = setTimeout(tick, 1200);
                                return;
                            }
                            activationRunId = '';
                            if (state === 'completed' && data.result) {
                                handleActivationConversationResult(data.result);
                                return;
                            }
                            appendBubble('assistant', (data && data.error) ? String(data.error) : (t().invalid_prefix + ' ' + t().invalid_suffix));
                            $status.text(t().reminder);
                            $composerWrap.addClass('is-attention');
                        },
                        error: function() {
                            activationRunId = '';
                            appendBubble('assistant', t().invalid_prefix + ' ' + t().invalid_suffix);
                            $status.text(t().reminder);
                            $composerWrap.addClass('is-attention');
                        },
                        complete: function() {
                            if (!activationRunId) {
                                setBusy(false);
                                $input.trigger('focus');
                            }
                        }
                    });
                }, 1200);
            }

            renderStaticCopy();
            appendIntro();

            $language.on('change', function() {
                currentLanguage = $(this).val() || 'en';
                renderStaticCopy();
                if ($log.find('.aysa-activation-bubble.user').length === 0) {
                    $log.empty();
                    appendIntro();
                }
            });

            $input.on('keydown', function(e){
                if (e.key === 'Enter' && !e.shiftKey) {
                    e.preventDefault();
                    $('#aysa-activation-chat-form').trigger('submit');
                }
            });

            $('#aysa-activation-chat-form').on('submit', function(e){
                e.preventDefault();
                const key = ($input.val() || '').trim();
                if (!key) {
                    if (!hasExplainedOffer) {
                        appendBubble('assistant', t().offer_intro);
                        appendBubble('assistant', t().offer_pricing);
                        appendBubble('assistant', t().offer_benefit);
                        appendBubble('assistant', t().offer_cta);
                        hasExplainedOffer = true;
                    } else {
                        appendBubble('assistant', t().empty);
                    }
                    $composerWrap.addClass('is-attention');
                    $status.text(t().reminder);
                    return;
                }

                appendBubble('user', '[secret key hidden]');
                $composerWrap.removeClass('is-attention');
                $input.val('');
                setBusy(true);
                $status.text(t().reminder);

                $.ajax({
                    url: ajaxUrl,
                    type: 'POST',
                    dataType: 'json',
                    data: {
                        action: 'aysa_conversation_async_start',
                        task: 'activation',
                        nonce: nonce,
                        site_host: host,
                        conversation_language: currentLanguage,
                        user_message: key
                    },
                    success: function(resp) {
                        if (!resp || !resp.success || !resp.data) {
                            appendBubble('assistant', (resp && resp.data && resp.data.message) ? String(resp.data.message) : (t().invalid_prefix + ' ' + t().invalid_suffix));
                            $status.text(t().reminder);
                            $composerWrap.addClass('is-attention');
                            return;
                        }
                        activationRunId = String(resp.data.run_id || '');
                        const state = resp.data.status ? String(resp.data.status) : '';
                        if (state === 'completed' && resp.data.result) {
                            activationRunId = '';
                            handleActivationConversationResult(resp.data.result);
                            setBusy(false);
                            $input.trigger('focus');
                            return;
                        }
                        $status.text(state === 'queued' ? 'Queued...' : 'Running...');
                        pollActivationConversationStatus(key);
                    },
                    error: function() {
                        appendBubble('assistant', t().invalid_prefix + ' ' + t().invalid_suffix);
                        $status.text(t().reminder);
                        $composerWrap.addClass('is-attention');
                        setBusy(false);
                        $input.trigger('focus');
                    }
                });
            });
        });
        </script>
        <?php
    }
    
    public function display_automation_page() {
        if (!$this->check_authentication()) {
            $aysa_section_title = 'Automation';
            include AYSA_PLUGIN_DIR . 'admin/partials/aysa-login-required.php';
            return;
        }
        include_once AYSA_PLUGIN_DIR . 'admin/partials/automation/aysa-automation-dashboard.php';
    }
    
    // AJAX handler to load automation content for the tab in Adjustment & Improvement
    public function handle_load_automation_content() {
        // Verify nonce
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_automation_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }

        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }
        
        // Check authentication
        if (!$this->check_authentication()) {
            wp_send_json_error('Authentication required');
            return;
        }
        
        // Capture the automation dashboard content
        ob_start();
        include_once AYSA_PLUGIN_DIR . 'admin/partials/automation/aysa-automation-dashboard.php';
        $content = ob_get_clean();
        
        // Remove the wrap div and h1 title since we're embedding this
        $content = preg_replace('/<div class="wrap">.*?<h1>.*?<\/h1>/s', '', $content);
        $content = preg_replace('/<\/div>\s*$/', '', $content);
        
        wp_send_json_success($content);
    }
    
    // Function to display the Welcome to Aysa page
    public function display_welcome_aysa_page() {
        if (!$this->check_authentication()) {
            $aysa_section_title = 'Welcome to AYSA';
            include AYSA_PLUGIN_DIR . 'admin/partials/aysa-login-required.php';
            return;
        }
        include_once AYSA_PLUGIN_DIR . 'admin/partials/aysa-welcome-page.php';
    }


    /**
     * Get the authenticated user's ID
     * @return int|null User ID if authenticated, null otherwise
     */
    private function get_user_id() {
        if (isset($_COOKIE['aysa_user_id'])) {
            return (int) $_COOKIE['aysa_user_id'];
        }
        return null;
    }

    /**
     * Handle logout action
     */
    public function handle_logout() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }

        if (!is_user_logged_in()) {
            wp_send_json_error(array('message' => 'Authentication required.'));
            return;
        }

        // Clear cookies
        setcookie('aysa_auth', '', time() - 3600, '/');
        setcookie('aysa_user_id', '', time() - 3600, '/');
        
        wp_send_json_success(array('message' => 'Logged out successfully'));
        exit;
    }

    /**
     * Handle saving wizard data from AJAX requests
     */
    public function handle_save_wizard_data() {
        $nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (empty($nonce) || (!wp_verify_nonce($nonce, 'aysa_wizard_nonce') && !wp_verify_nonce($nonce, 'aysa_nonce'))) {
            wp_send_json_error(array('message' => 'Security check failed'));
            return;
        }

        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions'));
            return;
        }
        
        // Check if user is authenticated
        if (!$this->check_authentication(false)) {
            wp_send_json_error(array('message' => 'Authentication required'));
            return;
        }

        $auth_manager = Aysa_Auth_Manager::get_instance();
        $auth_status = $auth_manager->is_authenticated();
        
        // Get the current step and data
        $step = isset($_POST['step']) ? sanitize_text_field((string) wp_unslash($_POST['step'])) : '';
        if (empty($step)) {
            wp_send_json_error(array('message' => 'Step is required'));
            return;
        }
        
        // Remove action and step from the data
        $data = wp_unslash($_POST);
        unset($data['action']);
        unset($data['step']);
        
        // Sanitize the data
        $sanitized_data = $this->sanitize_wizard_data($data);
        
        // Save the data for this step
        $user_id = isset($auth_status['aysa_user_id']) ? intval($auth_status['aysa_user_id']) : 0;
        if ($user_id <= 0) {
            wp_send_json_error(array('message' => 'Invalid authentication context'));
            return;
        }
        $option_name = 'aysa_wizard_' . $step . '_' . $user_id;
        update_option($option_name, $sanitized_data);
        
        // Return success
        wp_send_json_success(array('message' => 'Data saved successfully'));
    }

    /**
     * Save tone of voice from Setup SEO Profile via authenticated server-side API proxy.
     */
    public function handle_setup_save_tone_of_voice() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }

        if (!$this->aysa_current_user_can_workspace()) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }

        $key_validation = $this->aysa_validate_test_access_key(
            $this->aysa_get_effective_access_key(isset($_POST['access_key']) ? sanitize_text_field(wp_unslash($_POST['access_key'])) : ''),
            isset($_POST['site_host']) ? sanitize_text_field(wp_unslash($_POST['site_host'])) : ''
        );
        if (empty($key_validation['valid'])) {
            wp_send_json_error(array(
                'message' => (string) ($key_validation['message'] ?? 'Access key is invalid.'),
                'code' => 'access_key_invalid',
            ));
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0);
        $selected_tone = isset($_POST['selected_tone']) ? sanitize_text_field(wp_unslash($_POST['selected_tone'])) : '';

        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Missing project ID.'));
            return;
        }
        if ($selected_tone === '') {
            wp_send_json_error(array('message' => 'Missing tone selection.'));
            return;
        }

        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }

        try {
            $auth_manager = Aysa_Auth_Manager::get_instance();
            $existing = $auth_manager->make_authenticated_request('api/ToneOfVoice/ByProject/' . $project_id, 'GET');

            $existing_id = 0;
            if (is_array($existing)) {
                if (!empty($existing['id'])) {
                    $existing_id = intval($existing['id']);
                } elseif (isset($existing['data']) && is_array($existing['data']) && !empty($existing['data']['id'])) {
                    $existing_id = intval($existing['data']['id']);
                }
            }

            $operation = 'create';
            $ok = false;
            if ($existing_id > 0) {
                $operation = 'update';
                $ok = $auth_manager->make_authenticated_request(
                    'api/ToneOfVoice/UpdateTone/' . $existing_id,
                    'PATCH',
                    array('selectedTone' => $selected_tone)
                );
            }

            if (!$ok) {
                $operation = 'create';
                $ok = $auth_manager->make_authenticated_request(
                    'api/ToneOfVoice',
                    'POST',
                    array(
                        'projectId' => $project_id,
                        'selectedTone' => $selected_tone,
                    )
                );
            }

            if (!$ok) {
                wp_send_json_error(array('message' => 'Could not save tone of voice via API.'));
                return;
            }

            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_setup_save_tone_of_voice',
                'setup_save_tone_of_voice',
                array(
                    'module' => 'setup_seo_profile',
                    'operation' => $operation,
                ),
                2,
                'api/ToneOfVoice'
            );

            wp_send_json_success(array(
                'message' => 'Tone of voice saved successfully.',
                'operation' => $operation,
                'access' => $key_validation,
            ));
        } catch (Exception $e) {
            wp_send_json_error(array('message' => 'Tone of voice save failed: ' . $e->getMessage()));
        }
    }

    /**
     * Generic setup wizard API proxy to keep browser calls inside WordPress/auth context.
     */
    public function handle_setup_api_proxy() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }

        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }

        if (!$this->check_authentication(false)) {
            wp_send_json_success(array(
                'ok' => true,
                'notifications' => array(),
                'pre_activation' => true,
            ));
            return;
        }

        $key_validation = $this->aysa_validate_test_access_key(
            $this->aysa_get_effective_access_key(isset($_POST['access_key']) ? sanitize_text_field(wp_unslash($_POST['access_key'])) : ''),
            isset($_POST['site_host']) ? sanitize_text_field(wp_unslash($_POST['site_host'])) : ''
        );
        if (empty($key_validation['valid'])) {
            wp_send_json_error(array(
                'message' => (string) ($key_validation['message'] ?? 'Access key is invalid.'),
                'code' => 'access_key_invalid',
            ));
            return;
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $endpoint_raw = isset($_POST['endpoint']) ? (string) wp_unslash($_POST['endpoint']) : '';
        $method = strtoupper(isset($_POST['method']) ? sanitize_text_field(wp_unslash($_POST['method'])) : 'GET');
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $payload_raw = isset($_POST['payload']) ? wp_unslash($_POST['payload']) : '';

        if ($endpoint_raw === '') {
            wp_send_json_error(array('message' => 'Missing endpoint.'));
            return;
        }
        if (!in_array($method, array('GET', 'POST', 'PUT', 'PATCH', 'DELETE'), true)) {
            wp_send_json_error(array('message' => 'Invalid method.'));
            return;
        }

        $endpoint = trim($endpoint_raw);
        if (preg_match('#^https?://#i', $endpoint)) {
            $parts = wp_parse_url($endpoint);
            $path = isset($parts['path']) ? (string) $parts['path'] : '';
            $query = isset($parts['query']) ? (string) $parts['query'] : '';
            $endpoint = ltrim($path, '/');
            if ($query !== '') {
                $endpoint .= '?' . $query;
            }
        }
        $endpoint = ltrim($endpoint, '/');
        if (strpos($endpoint, 'api/') !== 0) {
            wp_send_json_error(array('message' => 'Blocked endpoint.'));
            return;
        }
        $proxy_path = strtok($endpoint, '?');
        $allowed_setup_proxy_paths = array(
            'api/ToneOfVoice',
            'api/ToneOfVoice/ByProject',
            'api/ToneOfVoice/UpdateTone',
        );
        $setup_proxy_allowed = false;
        foreach ($allowed_setup_proxy_paths as $allowed_path) {
            if ($proxy_path === $allowed_path || strpos($proxy_path, $allowed_path . '/') === 0) {
                $setup_proxy_allowed = true;
                break;
            }
        }
        if (!$setup_proxy_allowed) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Release-reviewed diagnostic only; the message contains no credentials or request payloads and runs only in opt-in debugging or the administrative uninstall lifecycle.
            error_log('[AYSA setup proxy allowlist warning] endpoint=' . $proxy_path);
            if ($this->aysa_agent8v3_beta_flag('AYSA_SETUP_API_PROXY_ALLOWLIST_ENFORCE', false)) {
                wp_send_json_error(array(
                    'message' => 'Endpoint is not allowlisted for Setup SEO Profile proxy.',
                    'code' => 'setup_proxy_endpoint_not_allowlisted',
                ));
                return;
            }
        }

        $payload = array();
        if ($payload_raw !== '') {
            if (is_array($payload_raw)) {
                $payload = $payload_raw;
            } elseif (is_string($payload_raw)) {
                $decoded = json_decode($payload_raw, true);
                if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                    $payload = $decoded;
                } else {
                    $payload = array();
                    parse_str($payload_raw, $payload);
                    if (!is_array($payload)) {
                        $payload = array();
                    }
                }
            }
        }

        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }

        try {
            $auth_manager = Aysa_Auth_Manager::get_instance();
            $response = $auth_manager->make_authenticated_request($endpoint, $method, $payload);
            if ($response === false) {
                wp_send_json_error(array('message' => 'API proxy request failed.'));
                return;
            }
            wp_send_json_success(array(
                'result' => $response,
                'access' => $key_validation,
            ));
        } catch (Exception $e) {
            wp_send_json_error(array('message' => 'API proxy exception: ' . $e->getMessage()));
        }
    }

    public function handle_setup_general_autofill() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }
        if (!$this->check_authentication(false)) {
            wp_send_json_success(array(
                'ok' => true,
                'notifications' => array(),
                'pre_activation' => true,
            ));
            return;
        }

        $key_validation = $this->aysa_validate_test_access_key(
            $this->aysa_get_effective_access_key(isset($_POST['access_key']) ? sanitize_text_field(wp_unslash($_POST['access_key'])) : ''),
            isset($_POST['site_host']) ? sanitize_text_field(wp_unslash($_POST['site_host'])) : ''
        );
        if (empty($key_validation['valid'])) {
            wp_send_json_error(array(
                'message' => (string) ($key_validation['message'] ?? 'Access key is invalid.'),
                'code' => 'access_key_invalid',
            ));
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0);
        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Missing project ID.'));
            return;
        }
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $force_generate_raw = isset($_POST['force_generate']) ? strtolower(trim((string) wp_unslash($_POST['force_generate']))) : '0';
        $force_generate = in_array($force_generate_raw, array('1', 'true', 'yes'), true);

        $requested_website = isset($_POST['website']) ? sanitize_text_field(wp_unslash($_POST['website'])) : home_url('/');
        $website = $this->aysa_setup_normalize_url($requested_website);
        if ($website === '') {
            wp_send_json_error(array('message' => 'Invalid website URL.'));
            return;
        }

        $auth_manager = Aysa_Auth_Manager::get_instance();
        $auth_status = $auth_manager->is_authenticated();
        $user_id = isset($auth_status['aysa_user_id']) ? intval($auth_status['aysa_user_id']) : 0;
        $general_option_name = ($user_id > 0) ? ('aysa_wizard_general_' . $user_id) : '';

        if (!$force_generate && $general_option_name !== '') {
            $saved = get_option($general_option_name, array());
            if (is_array($saved) && !empty($saved)) {
                $saved_data = array(
                    'id' => 0,
                    'companyName' => isset($saved['company_name']) ? (string) $saved['company_name'] : '',
                    'brand' => isset($saved['brand_name']) ? (string) $saved['brand_name'] : '',
                    'companyDescription' => isset($saved['company_description']) ? (string) $saved['company_description'] : '',
                    'supportPhoneNumber' => isset($saved['support_phone']) ? (string) $saved['support_phone'] : '',
                    'supportEmailAddress' => isset($saved['support_email']) ? (string) $saved['support_email'] : '',
                    'websiteUrl' => isset($saved['website']) ? (string) $saved['website'] : $website,
                    'websiteType' => isset($saved['website_type']) ? (string) $saved['website_type'] : '',
                    'shippingOptions' => isset($saved['shipping']) ? (string) $saved['shipping'] : '',
                    'paymentMethods' => isset($saved['payment_method']) ? (string) $saved['payment_method'] : '',
                    'industry' => isset($saved['industry']) ? (string) $saved['industry'] : '',
                    'businessInformation' => isset($saved['company_info']) ? (string) $saved['company_info'] : '',
                    'projectId' => $project_id,
                    'requestedWebsiteUrl' => $website,
                    'source' => 'setup_general_autofill_saved_wizard',
                    '_debug' => array(
                        'mode' => 'saved_wizard_data',
                        'force_generate' => false,
                    ),
                );

                $saved_value_count = 0;
                foreach (array('companyName', 'brand', 'companyDescription', 'supportPhoneNumber', 'supportEmailAddress', 'websiteUrl', 'websiteType', 'shippingOptions', 'paymentMethods', 'industry', 'businessInformation') as $key) {
                    if (trim((string) $saved_data[$key]) !== '') {
                        $saved_value_count++;
                    }
                }

                $contact_present = (
                    trim((string) $saved_data['supportPhoneNumber']) !== ''
                    || trim((string) $saved_data['supportEmailAddress']) !== ''
                );
                $commercial_present = (
                    trim((string) $saved_data['shippingOptions']) !== ''
                    && trim((string) $saved_data['paymentMethods']) !== ''
                );
                $profile_present = (
                    trim((string) $saved_data['companyDescription']) !== ''
                    || trim((string) $saved_data['businessInformation']) !== ''
                );

                $has_minimum_saved_profile = (
                    $saved_value_count >= 8
                    && trim((string) $saved_data['companyName']) !== ''
                    && trim((string) $saved_data['brand']) !== ''
                    && trim((string) $saved_data['websiteUrl']) !== ''
                    && trim((string) $saved_data['websiteType']) !== ''
                    && trim((string) $saved_data['industry']) !== ''
                    && $contact_present
                    && $commercial_present
                    && $profile_present
                );

                if ($has_minimum_saved_profile) {
                    wp_send_json_success($saved_data);
                    return;
                }
            }
        }

        // Local environments may opt in to an explicit crawl target without coupling the public build to a customer site.
        $website_for_autofill = $website;
        $website_host = strtolower((string) wp_parse_url($website_for_autofill, PHP_URL_HOST));
        if ($this->aysa_setup_is_local_host($website_host) && defined('AYSA_LOCAL_AUTOFILL_URL')) {
            $local_autofill_url = esc_url_raw(trim((string) AYSA_LOCAL_AUTOFILL_URL));
            if ($local_autofill_url !== '') {
                $website_for_autofill = $local_autofill_url;
            }
        }

        $data = array();
        $broker_payload = array(
            'project_id' => $project_id,
            'website' => $website_for_autofill,
            'requested_website' => $website,
            'site_host' => isset($_POST['site_host']) ? sanitize_text_field(wp_unslash($_POST['site_host'])) : '',
        );
        $api_resp = $this->aysa_broker_request('POST', 'v1/setup/general/autofill', $broker_payload, 45);
        $broker_debug = array(
            'ok' => false,
            'error_code' => '',
            'error_message' => '',
        );
        if (is_wp_error($api_resp)) {
            $broker_debug['error_code'] = (string) $api_resp->get_error_code();
            $broker_debug['error_message'] = (string) $api_resp->get_error_message();
        }
        if (!is_wp_error($api_resp) && is_array($api_resp) && !empty($api_resp['ok']) && isset($api_resp['data']) && is_array($api_resp['data'])) {
            $broker_debug['ok'] = true;
            $data = $api_resp['data'];
        }

        if (empty($data)) {
            $data = $this->aysa_setup_extract_general_info($website);
            $data['source'] = 'setup_general_autofill_local_fallback';
        }

        $resolved_website_type = isset($data['websiteType']) ? sanitize_key((string) $data['websiteType']) : '';
        $resolved_payment_methods = isset($data['paymentMethods']) ? trim((string) $data['paymentMethods']) : '';
        if ($resolved_payment_methods === '' && $resolved_website_type === 'ecommerce') {
            $data['paymentMethods'] = 'Card, Bank transfer, Cash on delivery';
            $data['_normalization'] = array(
                'payment_methods' => 'defaulted_for_ecommerce',
            );
        }

        $data['projectId'] = $project_id;
        $data['requestedWebsiteUrl'] = $website;
        $data['websiteUrl'] = isset($data['websiteUrl']) && is_string($data['websiteUrl']) && $data['websiteUrl'] !== ''
            ? $data['websiteUrl']
            : $website_for_autofill;
        $data['_debug'] = array(
            'broker_endpoint' => 'v1/setup/general/autofill',
            'requested_website' => $website,
            'website_used_for_autofill' => $website_for_autofill,
            'broker' => $broker_debug,
        );
        $cfg_probe = $this->aysa_get_broker_config();
        if (!is_wp_error($cfg_probe)) {
            $data['_debug']['broker_base_url'] = isset($cfg_probe['base_url']) ? (string) $cfg_probe['base_url'] : '';
            $data['_debug']['broker_key_id'] = isset($cfg_probe['key_id']) ? (string) $cfg_probe['key_id'] : '';
        }

        if ($general_option_name !== '' && is_array($data)) {
            $persist = array(
                'company_name' => isset($data['companyName']) ? sanitize_text_field((string) $data['companyName']) : '',
                'brand_name' => isset($data['brand']) ? sanitize_text_field((string) $data['brand']) : '',
                'company_description' => isset($data['companyDescription']) ? sanitize_textarea_field((string) $data['companyDescription']) : '',
                'support_phone' => isset($data['supportPhoneNumber']) ? sanitize_text_field((string) $data['supportPhoneNumber']) : '',
                'support_email' => isset($data['supportEmailAddress']) ? sanitize_email((string) $data['supportEmailAddress']) : '',
                'website' => isset($data['websiteUrl']) ? esc_url_raw((string) $data['websiteUrl']) : '',
                'website_type' => isset($data['websiteType']) ? sanitize_text_field((string) $data['websiteType']) : '',
                'shipping' => isset($data['shippingOptions']) ? sanitize_text_field((string) $data['shippingOptions']) : '',
                'payment_method' => isset($data['paymentMethods']) ? sanitize_text_field((string) $data['paymentMethods']) : '',
                'industry' => isset($data['industry']) ? sanitize_text_field((string) $data['industry']) : '',
                'company_info' => isset($data['businessInformation']) ? sanitize_textarea_field((string) $data['businessInformation']) : '',
            );
            update_option($general_option_name, $persist, false);
        }

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_setup_general_autofill',
            'setup_general_autofill',
            array(
                'module' => 'setup_seo_profile',
                'source' => isset($data['source']) ? sanitize_text_field((string) $data['source']) : '',
                'force_generate' => $force_generate ? 1 : 0,
                'broker_ok' => !empty($broker_debug['ok']) ? 1 : 0,
                'website' => $website_for_autofill,
            ),
            55,
            'v1/setup/general/autofill'
        );

        wp_send_json_success($data);
    }

    public function handle_setup_conversation() {
        $request_started_at = microtime(true);
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }
        if (!$this->check_authentication(false)) {
            wp_send_json_error(array('message' => 'Authentication required.'));
            return;
        }

        $key_validation = $this->aysa_validate_test_access_key(
            $this->aysa_get_effective_access_key(isset($_POST['access_key']) ? sanitize_text_field(wp_unslash($_POST['access_key'])) : ''),
            isset($_POST['site_host']) ? sanitize_text_field(wp_unslash($_POST['site_host'])) : ''
        );
        if (empty($key_validation['valid'])) {
            wp_send_json_error(array(
                'message' => (string) ($key_validation['message'] ?? 'Access key is invalid.'),
                'code' => 'access_key_invalid',
            ));
            return;
        }

        $mode = isset($_POST['mode']) ? sanitize_key((string) wp_unslash($_POST['mode'])) : 'bootstrap';
        $requested_mode = $mode;
        $current_step = isset($_POST['current_step']) ? sanitize_key((string) wp_unslash($_POST['current_step'])) : 'integrations';
        $conversation_language = isset($_POST['conversation_language']) ? sanitize_key((string) wp_unslash($_POST['conversation_language'])) : 'en';
        if (!in_array($conversation_language, array('en', 'ro', 'bg'), true)) {
            $conversation_language = 'en';
        }
        $project_id = $this->aysa_resolve_installed_project_id(isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0);
        $user_message = isset($_POST['user_message']) ? sanitize_textarea_field((string) wp_unslash($_POST['user_message'])) : '';

        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Missing project ID.'));
            return;
        }

        $auth_manager = Aysa_Auth_Manager::get_instance();
        $auth_status = $auth_manager->is_authenticated();
        $user_id = isset($auth_status['aysa_user_id']) ? intval($auth_status['aysa_user_id']) : 0;
        if ($user_id <= 0) {
            wp_send_json_error(array('message' => 'Invalid authentication context.'));
            return;
        }

        $onboarding_run_token = $this->aysa_get_or_create_onboarding_run_token($project_id);

        $setup_reset_forced = $this->aysa_is_setup_reset_forced();
        $local_setup_state = $setup_reset_forced ? array() : $this->aysa_get_local_setup_state_for_user($user_id, $project_id);
        $general = isset($local_setup_state['general']) && is_array($local_setup_state['general']) ? $local_setup_state['general'] : array();
        $website = isset($general['website']) ? $this->aysa_setup_normalize_url((string) $general['website']) : '';
        if ($website === '') {
            $website = $this->aysa_setup_normalize_url(home_url('/'));
        }

        $google_connected = false;
        $google_email = '';
        $google_gsc_count = 0;
        $google_ga4_count = 0;
        if (class_exists('Aysa_Google_OAuth_Manager')) {
            $oauth_manager = Aysa_Google_OAuth_Manager::get_instance();
            if (is_object($oauth_manager) && method_exists($oauth_manager, 'get_status_for_project')) {
                $oauth_status = $oauth_manager->get_status_for_project($project_id);
                if (!is_wp_error($oauth_status) && is_array($oauth_status)) {
                    $google_connected = !empty($oauth_status['connected']);
                    $google_email = isset($oauth_status['email']) ? sanitize_email((string) $oauth_status['email']) : '';
                    $google_gsc_count = isset($oauth_status['gsc_sites_count']) ? intval($oauth_status['gsc_sites_count']) : 0;
                    $google_ga4_count = isset($oauth_status['ga4_properties_count']) ? intval($oauth_status['ga4_properties_count']) : 0;
                }
            }
        }
        $endpoint = '/v1/conversation/setup/bootstrap';
        $payload = array(
            'project_id' => $project_id,
            'current_step' => $current_step,
            'reset_session' => !empty($_POST['reset_session']),
            'skip_google' => !empty($_POST['skip_google']),
            'conversation_language' => $conversation_language,
            'site_host' => isset($_POST['site_host']) ? sanitize_text_field(wp_unslash($_POST['site_host'])) : '',
            'website' => $website,
            'onboarding_run_token' => $onboarding_run_token,
            'wallet_snapshot' => $this->aysa_build_agent_wallet_snapshot($project_id),
            'local_setup_state' => $local_setup_state,
            'ui_context' => array(
                'conversation_language' => $conversation_language,
                'available_languages' => array('en', 'ro', 'bg'),
                'integrations' => array(
                    'terms_url' => 'https://aysa.ai/terms/',
                    'privacy_url' => 'https://aysa.ai/privacy/',
                    'google_connect_available' => true,
                    'google_connect_label' => 'Connect Google',
                    'google_connected' => $google_connected,
                    'google_skipped' => !empty($_POST['skip_google']),
                    'google_email' => $google_email,
                    'google_gsc_count' => $google_gsc_count,
                    'google_ga4_count' => $google_ga4_count,
                ),
            ),
        );

        if ($mode === 'message') {
            $endpoint = '/v1/conversation/setup/message';
            $payload['user_message'] = $user_message;
        }

        if ($this->aysa_should_queue_setup_competition_suggestion($mode, $current_step, $user_message)) {
            $plugin_url = admin_url('admin.php?page=aysa-agent8-v3-lab');
            $payload['async_notification'] = array(
                'channel' => 'setup_seo_profile',
                'event_key' => 'competition_seed_research_ready',
                'ui_action' => 'resume_setup_onboarding',
                'cta_label' => $conversation_language === 'ro' ? 'Vezi sugestiile' : ($conversation_language === 'bg' ? 'Виж предложенията' : 'See the suggestions'),
                'action_url' => $plugin_url,
                'email_to' => $this->get_notification_target_email(),
                'conversation_language' => $conversation_language,
                'run_token' => $onboarding_run_token,
            );

            register_shutdown_function(function() use ($endpoint, $payload) {
                $this->aysa_broker_request_async('POST', $endpoint, $payload, 1);
            });

            $assistant_message = $this->aysa_setup_competition_async_ack($conversation_language);
            $email_target = trim((string) ($payload['async_notification']['email_to'] ?? ''));
            wp_send_json_success(array(
                'result' => array(
                    'ok' => true,
                    'assistant_message' => $assistant_message,
                    'phase' => 'onboarding_competition',
                    'current_step' => 'competition',
                    'next_step' => 'competition',
                    'step_substate' => 'regenerating_proposal',
                    'onboarding_run_token' => $onboarding_run_token,
                    'turn_type' => 'provide_seed_keywords',
                    'intent_family' => 'does_not_know',
                    'assistant_goal' => 'answer_and_reanchor',
                    'next_action' => 'await_async_competition_proposal',
                    'confidence' => 1,
                    'needs_clarification' => false,
                    'reanchor_target' => 'competition',
                    'async_task' => array(
                        'type' => 'competition_seed_research',
                        'status' => 'running',
                        'channel' => 'setup_seo_profile',
                        'event_key' => 'competition_seed_research_ready',
                    ),
                    'notification_policy' => array(
                        'in_app' => true,
                        'email' => $email_target !== '',
                        'channel' => 'setup_seo_profile',
                        'event_key' => 'competition_seed_research_ready',
                        'action_url' => $plugin_url,
                    ),
                    'ready_to_advance' => false,
                    'actions' => array(
                        'executed' => array(),
                        'should_advance' => false,
                    ),
                    'messages' => array(
                        array('role' => 'user', 'message' => $user_message),
                        array('role' => 'assistant', 'message' => $assistant_message),
                    ),
                    'suggested_actions' => array(),
                    'state' => array(
                        'steps' => is_array($local_setup_state) ? $local_setup_state : array(),
                        'meta' => array(
                            'phase' => 'onboarding_competition',
                            'current_step' => 'competition',
                            'next_step' => 'competition',
                            'step_substate' => 'regenerating_proposal',
                            'onboarding_run_token' => $onboarding_run_token,
                            'competition_research_status' => 'running',
                            'intent_family' => 'does_not_know',
                            'assistant_goal' => 'answer_and_reanchor',
                            'last_resolved_action' => 'await_async_competition_proposal',
                            'last_reanchor_target' => 'competition',
                            'turn_confidence' => 1,
                            'needs_clarification' => false,
                        ),
                    ),
                ),
                'access' => $key_validation,
                'queued' => true,
            ));
            return;
        }

        $broker_started_at = microtime(true);
        $result = $this->aysa_broker_request('POST', $endpoint, $payload, 75);
        $broker_elapsed_ms = (int) round((microtime(true) - $broker_started_at) * 1000);
        if (is_wp_error($result)) {
            wp_send_json_error(array('message' => $result->get_error_message()));
            return;
        }
        if (!is_array($result) || empty($result['ok'])) {
            wp_send_json_error(array(
                'message' => isset($result['error']) ? (string) $result['error'] : 'Setup conversation request failed.',
                'result' => $result,
            ));
            return;
        }

        $self = $this;
        register_shutdown_function(static function () use (
            $self,
            $mode,
            $project_id,
            $endpoint,
            $result,
            $user_id,
            $conversation_language,
            $google_connected,
            $broker_elapsed_ms,
            $request_started_at
        ) {
            if ($mode === 'message') {
                $self->aysa_wallet_bill_provider_usage(
                    $project_id,
                    $endpoint,
                    isset($result['usage']) && is_array($result['usage']) ? $result['usage'] : array(),
                    'setup_conversation_message',
                    array(
                        'module' => 'setup_conversation',
                        'source' => 'chat_message',
                    )
                );
            }

            if (!empty($result['wp_updates']) && is_array($result['wp_updates'])) {
                $self->aysa_apply_setup_conversation_updates($user_id, $result['wp_updates'], $project_id);
                $updated_local_setup_state = $self->aysa_get_local_setup_state_for_user($user_id, $project_id);
                $result_state = isset($result['state']) && is_array($result['state']) ? $result['state'] : array();
                $result_meta = isset($result_state['meta']) && is_array($result_state['meta']) ? $result_state['meta'] : array();
                $result_steps = isset($result_state['steps']) && is_array($result_state['steps']) ? $result_state['steps'] : array();
                $google_skipped = !empty($result_steps['integrations']['google_skip'])
                    || !empty($result_meta['google_optional_skipped']);

                $self->aysa_sync_setup_state_to_client_rag(
                    $project_id,
                    $conversation_language,
                    $updated_local_setup_state,
                    $google_connected,
                    $google_skipped,
                    $result_meta
                );
            }

            if (defined('WP_DEBUG') && WP_DEBUG) {
                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Release-reviewed diagnostic only; the message contains no credentials or request payloads and runs only in opt-in debugging or the administrative uninstall lifecycle.
                error_log(sprintf(
                    'AYSA setup conversation timings: broker=%dms total=%dms mode=%s project=%d',
                    $broker_elapsed_ms,
                    (int) round((microtime(true) - $request_started_at) * 1000),
                    (string) $mode,
                    (int) $project_id
                ));
            }
        });

        wp_send_json_success(array(
            'result' => $result,
            'access' => $key_validation,
        ));
    }

    public function handle_adjustments_conversation() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }
        if (!$this->check_authentication(false)) {
            wp_send_json_error(array('message' => 'Authentication required.'));
            return;
        }

        $key_validation = $this->aysa_validate_test_access_key(
            $this->aysa_get_effective_access_key(isset($_POST['access_key']) ? sanitize_text_field((string) wp_unslash($_POST['access_key'])) : ''),
            isset($_POST['site_host']) ? sanitize_text_field(wp_unslash($_POST['site_host'])) : ''
        );
        if (empty($key_validation['valid'])) {
            wp_send_json_error(array(
                'message' => (string) ($key_validation['message'] ?? 'Access key is invalid.'),
                'code' => 'access_key_invalid',
            ));
            return;
        }

        $mode = isset($_POST['mode']) ? sanitize_key((string) wp_unslash($_POST['mode'])) : 'bootstrap';
        $conversation_language = isset($_POST['conversation_language']) ? sanitize_key((string) wp_unslash($_POST['conversation_language'])) : 'en';
        if (!in_array($conversation_language, array('en', 'ro', 'bg'), true)) {
            $conversation_language = 'en';
        }
        $project_id = $this->aysa_resolve_installed_project_id(isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0);
        $user_message = isset($_POST['user_message']) ? sanitize_textarea_field((string) wp_unslash($_POST['user_message'])) : '';

        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Missing project ID.'));
            return;
        }

        $auth_manager = Aysa_Auth_Manager::get_instance();
        $auth_status = $auth_manager->is_authenticated();
        $user_id = isset($auth_status['aysa_user_id']) ? intval($auth_status['aysa_user_id']) : 0;
        if ($user_id <= 0) {
            wp_send_json_error(array('message' => 'Invalid authentication context.'));
            return;
        }

        $setup_reset_forced = $this->aysa_is_setup_reset_forced();
        $local_setup_state = $setup_reset_forced ? array() : $this->aysa_get_local_setup_state_for_user($user_id, $project_id);
        $general = isset($local_setup_state['general']) && is_array($local_setup_state['general']) ? $local_setup_state['general'] : array();
        $website = isset($general['website']) ? $this->aysa_setup_normalize_url((string) $general['website']) : '';
        if ($website === '') {
            $website = $this->aysa_setup_normalize_url(home_url('/'));
        }
        $google_connected = false;
        $google_email = '';
        $google_gsc_count = 0;
        $google_ga4_count = 0;
        if (class_exists('Aysa_Google_OAuth_Manager')) {
            $oauth_manager = Aysa_Google_OAuth_Manager::get_instance();
            if (is_object($oauth_manager) && method_exists($oauth_manager, 'get_status_for_project')) {
                $oauth_status = $oauth_manager->get_status_for_project($project_id);
                if (!is_wp_error($oauth_status) && is_array($oauth_status)) {
                    $google_connected = !empty($oauth_status['connected']);
                    $google_email = isset($oauth_status['email']) ? sanitize_email((string) $oauth_status['email']) : '';
                    $google_gsc_count = isset($oauth_status['gsc_sites_count']) ? intval($oauth_status['gsc_sites_count']) : 0;
                    $google_ga4_count = isset($oauth_status['ga4_properties_count']) ? intval($oauth_status['ga4_properties_count']) : 0;
                }
            }
        }
        $research_snapshot = $setup_reset_forced ? array() : $this->aysa_adjustments_get_research_snapshot($project_id, $user_id);
        $research_snapshot_for_ai = $setup_reset_forced ? array() : $this->aysa_adjustments_reduce_research_snapshot_for_ai($research_snapshot);
        $client_state = array();
        if (!$setup_reset_forced && isset($_POST['client_state'])) {
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
            $decoded_client_state = json_decode((string) wp_unslash($_POST['client_state']), true);
            if (is_array($decoded_client_state)) {
                $client_state = $decoded_client_state;
            }
        }
        $client_local_setup_state = isset($client_state['local_setup_state']) && is_array($client_state['local_setup_state'])
            ? $client_state['local_setup_state']
            : array();
        if ($client_local_setup_state !== array()) {
            $local_setup_state = $this->aysa_merge_local_setup_state_fallback($local_setup_state, $client_local_setup_state);
        }
        $forced_execution_action = isset($_POST['forced_execution_action']) ? sanitize_text_field((string) wp_unslash($_POST['forced_execution_action'])) : '';

        if ($mode === 'message' && $forced_execution_action !== '') {
            $result = array(
                'ok' => true,
                'state' => is_array($client_state) ? $client_state : array(),
                'messages' => array(),
            );
            $execution_meta = $this->aysa_adjustments_execute_research_action($forced_execution_action, $project_id, $user_id, $conversation_language);
            $research_snapshot = $this->aysa_adjustments_get_research_snapshot($project_id, $user_id);
            $assistant_message = !empty($execution_meta['assistant_message'])
                ? (string) $execution_meta['assistant_message']
                : '';
            if ($user_message !== '') {
                $result['messages'][] = array('role' => 'user', 'message' => $user_message);
            }
            if ($assistant_message !== '') {
                $result['messages'][] = array('role' => 'assistant', 'message' => $assistant_message);
                $result['assistant_message'] = $assistant_message;
            }
            if (!empty($execution_meta['suggested_actions']) && is_array($execution_meta['suggested_actions'])) {
                $result['suggested_actions'] = $execution_meta['suggested_actions'];
            }
            if (!empty($execution_meta['ui_action'])) {
                $result['ui_action'] = (string) $execution_meta['ui_action'];
            }
            if (!empty($execution_meta['payment_link'])) {
                $result['payment_link'] = (string) $execution_meta['payment_link'];
            }
            if (!isset($result['state']['context']) || !is_array($result['state']['context'])) {
                $result['state']['context'] = array();
            }
            if (!isset($result['state']['meta']) || !is_array($result['state']['meta'])) {
                $result['state']['meta'] = array();
            }
            $result['state']['context']['research_snapshot'] = $research_snapshot;
            if (!empty($execution_meta['module'])) {
                $result['state']['meta']['active_research_module'] = (string) $execution_meta['module'];
            }
            if (!empty($execution_meta['state_meta']) && is_array($execution_meta['state_meta'])) {
                foreach ($execution_meta['state_meta'] as $meta_key => $meta_value) {
                    if (!is_string($meta_key) || $meta_key === '') {
                        continue;
                    }
                    $result['state']['meta'][$meta_key] = $meta_value;
                }
            }
            $result['research_snapshot'] = $research_snapshot;
            $result['workspace_artifact'] = $this->aysa_adjustments_build_research_workspace_artifact($research_snapshot, $conversation_language);

            wp_send_json_success(array(
                'result' => $result,
                'access' => $key_validation,
            ));
            return;
        }

        $endpoint = '/v1/conversation/adjustments/bootstrap';
        $payload = array(
            'project_id' => $project_id,
            'aysa_user_id' => $user_id,
            'conversation_language' => $conversation_language,
            'reset_session' => !empty($_POST['reset_session']),
            'skip_google' => !empty($_POST['skip_google']),
            'site_host' => isset($_POST['site_host']) ? sanitize_text_field((string) wp_unslash($_POST['site_host'])) : '',
            'website' => $website,
            'wallet_snapshot' => $this->aysa_build_agent_wallet_snapshot($project_id),
            'local_setup_state' => $local_setup_state,
            'research_snapshot' => $research_snapshot_for_ai,
            'ui_context' => array(
                'conversation_language' => $conversation_language,
                'available_languages' => array('en', 'ro', 'bg'),
                'phase' => 'agent_orchestration',
                'integrations' => array(
                    'terms_url' => 'https://aysa.ai/terms/',
                    'privacy_url' => 'https://aysa.ai/privacy/',
                    'google_connect_available' => true,
                    'google_connect_label' => 'Connect Google',
                    'google_connected' => $google_connected,
                    'google_skipped' => !empty($_POST['skip_google']),
                    'google_email' => $google_email,
                    'google_gsc_count' => $google_gsc_count,
                    'google_ga4_count' => $google_ga4_count,
                ),
            ),
            'state' => $client_state,
        );

        if ($mode === 'message') {
            $endpoint = '/v1/conversation/adjustments/message';
            $payload['user_message'] = $user_message;
        }

        $result = $this->aysa_broker_request('POST', $endpoint, $payload, 75);
        if (is_wp_error($result)) {
            wp_send_json_error(array('message' => $result->get_error_message()));
            return;
        }
        if (!is_array($result) || empty($result['ok'])) {
            wp_send_json_error(array(
                'message' => isset($result['error']) ? (string) $result['error'] : 'Adjustments conversation request failed.',
                'result' => $result,
            ));
            return;
        }

        if ($mode === 'message') {
            $this->aysa_wallet_bill_provider_usage(
                $project_id,
                $endpoint,
                isset($result['usage']) && is_array($result['usage']) ? $result['usage'] : array(),
                'adjustments_conversation_message',
                array(
                    'module' => 'adjustments_conversation',
                    'source' => 'chat_message',
                )
            );
        }

        $execution_action = (string) ($result['execution_action'] ?? '');
        if ($execution_action !== '') {
            $execution_meta = $this->aysa_adjustments_execute_research_action($execution_action, $project_id, $user_id, $conversation_language);
            $research_snapshot = $this->aysa_adjustments_get_research_snapshot($project_id, $user_id);
            if (!empty($execution_meta['assistant_message'])) {
                $result['assistant_message'] = $execution_meta['assistant_message'];
            }
            if (!empty($execution_meta['suggested_actions']) && is_array($execution_meta['suggested_actions'])) {
                $result['suggested_actions'] = $execution_meta['suggested_actions'];
            }
            if (!empty($execution_meta['ui_action'])) {
                $result['ui_action'] = (string) $execution_meta['ui_action'];
            }
            if (!empty($execution_meta['payment_link'])) {
                $result['payment_link'] = (string) $execution_meta['payment_link'];
            }
            if (!isset($result['state']) || !is_array($result['state'])) {
                $result['state'] = array();
            }
            if (!isset($result['state']['context']) || !is_array($result['state']['context'])) {
                $result['state']['context'] = array();
            }
            if (!isset($result['state']['meta']) || !is_array($result['state']['meta'])) {
                $result['state']['meta'] = array();
            }
            $result['state']['context']['research_snapshot'] = $research_snapshot;
            if (!empty($execution_meta['module'])) {
                $result['state']['meta']['active_research_module'] = (string) $execution_meta['module'];
            }
            if (!empty($execution_meta['state_meta']) && is_array($execution_meta['state_meta'])) {
                foreach ($execution_meta['state_meta'] as $meta_key => $meta_value) {
                    if (!is_string($meta_key) || $meta_key === '') {
                        continue;
                    }
                    $result['state']['meta'][$meta_key] = $meta_value;
                }
            }
            if ($execution_action === 'keyword_research_sync' && empty($result['state']['meta']['keyword_walkthrough_stage'])) {
                $result['state']['meta']['keyword_walkthrough_stage'] = 'ranking';
            }
        } else {
            $research_snapshot = $this->aysa_adjustments_get_research_snapshot($project_id, $user_id);
            if (isset($result['state']) && is_array($result['state'])) {
                if (!isset($result['state']['context']) || !is_array($result['state']['context'])) {
                    $result['state']['context'] = array();
                }
                $result['state']['context']['research_snapshot'] = $research_snapshot;
            }
        }
        $result['research_snapshot'] = $research_snapshot;
        $result['workspace_artifact'] = $this->aysa_adjustments_build_research_workspace_artifact($research_snapshot, $conversation_language);

        if (isset($result['state']['context']['local_setup_state']) && is_array($result['state']['context']['local_setup_state'])) {
            $this->aysa_persist_local_setup_state_snapshot($user_id, $result['state']['context']['local_setup_state'], $project_id);
        }

        wp_send_json_success(array(
            'result' => $result,
            'access' => $key_validation,
        ));
    }

    public function handle_keyword_research_run_status() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }
        if (!$this->check_authentication(false)) {
            wp_send_json_error(array('message' => 'Authentication required.'));
            return;
        }

        $project_id = isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : intval(get_option('aysa_project_id', 0));
        $project_id = $this->aysa_resolve_project_id_with_local_fallback($project_id);
        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Missing project ID.'));
            return;
        }

        $run_id = isset($_POST['run_id']) ? intval((string) wp_unslash($_POST['run_id'])) : 0;
        $after_event_id = isset($_POST['after_event_id']) ? intval((string) wp_unslash($_POST['after_event_id'])) : 0;
        $job_type = isset($_POST['job_type']) ? sanitize_key((string) wp_unslash($_POST['job_type'])) : '';
        $status_endpoint = isset($_POST['status_endpoint']) ? sanitize_text_field((string) wp_unslash($_POST['status_endpoint'])) : '/v1/research-runs/keyword/status';
        if (($status_endpoint === '' || $status_endpoint === '/v1/research-runs/keyword/status') && $job_type !== '' && $job_type !== 'keyword_research') {
            if (strpos($job_type, 'business_profile') !== false) {
                $status_endpoint = '/v1/setup/general/autofill/status';
            } elseif (strpos($job_type, 'site_audit') !== false) {
                $status_endpoint = '/v1/research-runs/technical-site-audit/status';
            } elseif (strpos($job_type, 'pagespeed') !== false) {
                $status_endpoint = '/v1/research-runs/technical-pagespeed/status';
            } elseif (strpos($job_type, 'schema') !== false) {
                $status_endpoint = '/v1/research-runs/technical-schema/status';
            } elseif (strpos($job_type, 'redirect') !== false) {
                $status_endpoint = '/v1/research-runs/technical-redirects/status';
            } elseif (strpos($job_type, 'sitemap') !== false || strpos($job_type, 'robots') !== false) {
                $status_endpoint = '/v1/research-runs/technical-sitemap-robots/status';
            } elseif (strpos($job_type, 'internal_link') !== false) {
                $status_endpoint = '/v1/internal-link-runs/status';
            } elseif (strpos($job_type, 'onpage') !== false) {
                $status_endpoint = '/v1/onpage-runs/status';
            }
            $status_endpoint_by_job = array(
                'business_profile_autofill' => '/v1/setup/general/autofill/status',
                'technical_site_audit' => '/v1/research-runs/technical-site-audit/status',
                'technical_pagespeed' => '/v1/research-runs/technical-pagespeed/status',
                'technical_schema' => '/v1/research-runs/technical-schema/status',
                'technical_redirects' => '/v1/research-runs/technical-redirects/status',
                'technical_sitemap_robots' => '/v1/research-runs/technical-sitemap-robots/status',
                'technical_optimization' => '/v1/research-runs/technical-optimization/status',
                'internal_linking' => '/v1/internal-link-runs/status',
                'onpage' => '/v1/onpage-runs/status',
            );
            if (isset($status_endpoint_by_job[$job_type])) {
                $status_endpoint = $status_endpoint_by_job[$job_type];
            }
        }
        $allowed_status_endpoints = array(
            '/v1/setup/general/autofill/status',
            '/v1/research-runs/keyword/status',
            '/v1/research-runs/technical-site-audit/status',
            '/v1/research-runs/technical-pagespeed/status',
            '/v1/research-runs/technical-schema/status',
            '/v1/research-runs/technical-redirects/status',
            '/v1/research-runs/technical-sitemap-robots/status',
            '/v1/research-runs/technical-optimization/status',
            '/v1/internal-link-runs/status',
            '/v1/onpage-runs/status',
        );
        if (!in_array($status_endpoint, $allowed_status_endpoints, true)) {
            wp_send_json_error(array('message' => 'Unsupported async status endpoint.'));
            return;
        }

        $result = $this->aysa_broker_request('POST', $status_endpoint, array(
            'project_id' => $project_id,
            'run_id' => $run_id,
            'after_event_id' => $after_event_id,
        ), 30);
        if (is_wp_error($result)) {
            wp_send_json_error(array('message' => $result->get_error_message()));
            return;
        }

        $projection = isset($result['projection']) && is_array($result['projection']) ? $result['projection'] : array();
        if ($status_endpoint === '/v1/research-runs/keyword/status' && !empty($projection) && !empty($result['run']['id'])) {
            $result['local_projection_sync'] = $this->aysa_keyword_research_apply_local_projection(
                $project_id,
                intval($result['run']['id']),
                $projection
            );
        }

        $wallet_snapshot = $this->aysa_build_agent_wallet_snapshot($project_id);
        if (isset($result['billing']) && is_array($result['billing'])) {
            $wallet_snapshot = $this->aysa_agent8_apply_top_level_billing_usage_hint($wallet_snapshot, $result);
        }

        wp_send_json_success(array(
            'status' => $result,
            'wallet_snapshot' => $wallet_snapshot,
        ));
    }

    private function aysa_read_projection_sync_payload($max_bytes = 262144) {
        $secret = $this->get_app_aysa_internal_secret();
        if ($secret === '') {
            wp_send_json_error(array(
                'message' => 'Internal secret unavailable.',
                'code' => 'missing_internal_secret',
                'diagnostic' => 'missing_internal_secret',
            ), 500);
            return null;
        }

        $provided = $this->aysa_get_bearer_authorization_token();
        if ($provided === '' || !hash_equals($secret, $provided)) {
            wp_send_json_error(array('message' => 'Unauthorized'), 401);
            return null;
        }

        $content_length = isset($_SERVER['CONTENT_LENGTH']) ? intval(wp_unslash($_SERVER['CONTENT_LENGTH'])) : 0;
        if ($content_length > intval($max_bytes)) {
            wp_send_json_error(array(
                'message' => 'Projection sync payload too large.',
                'code' => 'payload_too_large',
                'diagnostic' => 'payload_too_large',
                'max_bytes' => intval($max_bytes),
            ), 413);
            return null;
        }

        $remote_addr = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field((string) wp_unslash($_SERVER['REMOTE_ADDR'])) : '';
        $rate_key = 'aysa_projection_sync_rate_' . substr(hash('sha256', $provided . '|' . $remote_addr), 0, 32);
        $rate_count = intval(get_transient($rate_key));
        if ($rate_count >= 60) {
            wp_send_json_error(array('message' => 'Projection sync rate limit exceeded.'), 429);
            return null;
        }
        set_transient($rate_key, $rate_count + 1, MINUTE_IN_SECONDS);

        $raw = file_get_contents('php://input');
        if (is_string($raw) && strlen($raw) > intval($max_bytes)) {
            wp_send_json_error(array(
                'message' => 'Projection sync payload too large.',
                'code' => 'payload_too_large',
                'diagnostic' => 'payload_too_large',
                'max_bytes' => intval($max_bytes),
            ), 413);
            return null;
        }

        $payload = json_decode(is_string($raw) ? $raw : '', true);
        if (!is_array($payload)) {
            wp_send_json_error(array(
                'message' => 'Invalid JSON payload',
                'code' => 'invalid_json_payload',
                'diagnostic' => 'invalid_json_payload',
            ), 400);
            return null;
        }

        return $payload;
    }

    /**
     * Projection sync is an internal site-bound write lane. Never fall back to
     * a caller-provided project id when the normal access-key resolver is
     * unavailable: the request must match this WordPress installation's
     * persisted project binding exactly.
     */
    private function aysa_resolve_projection_sync_project_id($payload) {
        $payload = is_array($payload) ? $payload : array();
        $requested_project_id = intval($payload['project_id'] ?? 0);
        $bound_project_id = intval(get_option('aysa_project_id', 0));
        if ($requested_project_id <= 0 || $bound_project_id <= 0 || $requested_project_id !== $bound_project_id) {
            return 0;
        }

        return $bound_project_id;
    }

    private function aysa_resolve_installed_project_id($requested_project_id = 0) {
        $bound_project_id = intval(get_option('aysa_project_id', 0));
        $requested_project_id = intval($requested_project_id);
        if ($bound_project_id <= 0 || ($requested_project_id > 0 && $requested_project_id !== $bound_project_id)) {
            return 0;
        }
        return $bound_project_id;
    }

    public function handle_keyword_research_projection_sync() {
        // Internal bearer auth is enforced in aysa_read_projection_sync_payload() via hash_equals().
        $payload = $this->aysa_read_projection_sync_payload();
        if (!is_array($payload)) {
            return;
        }

        $project_id = $this->aysa_resolve_projection_sync_project_id($payload);
        $run_id = intval($payload['run_id'] ?? 0);
        $projection = isset($payload['projection']) && is_array($payload['projection']) ? $payload['projection'] : array();
        if ($project_id <= 0 || $run_id <= 0 || $projection === array()) {
            wp_send_json_error(array('message' => 'Invalid projection sync payload'), 422);
            return;
        }

        $sync = $this->aysa_keyword_research_apply_local_projection($project_id, $run_id, $projection);
        wp_send_json_success(array(
            'local_projection_sync' => $sync,
        ));
    }

    public function handle_technical_site_audit_projection_sync() {
        // Internal bearer auth is enforced in aysa_read_projection_sync_payload() via hash_equals().
        $payload = $this->aysa_read_projection_sync_payload(1048576);
        if (!is_array($payload)) {
            return;
        }

        $project_id = $this->aysa_resolve_projection_sync_project_id($payload);
        $operation = sanitize_key((string) ($payload['operation'] ?? ''));
        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Invalid project id'), 422);
            return;
        }

        if ($operation === 'preflight') {
            wp_send_json_success(array(
                'preflight' => array(
                    'status' => 'ready',
                    'endpoint' => 'technical_site_audit',
                    'project_id' => $project_id,
                ),
            ));
            return;
        }

        if ($operation === 'reset_state') {
            $sync = $this->aysa_technical_site_audit_reset_local_projection($project_id);
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'update_issue_lifecycle') {
            $issue_id = sanitize_text_field((string) ($payload['issue_id'] ?? ''));
            $lifecycle_state = sanitize_key((string) ($payload['lifecycle_state'] ?? ''));
            $sync = $this->aysa_technical_site_audit_update_issue_lifecycle($project_id, $issue_id, $lifecycle_state);
            if (empty($sync['applied'])) {
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
                'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
            ));
            return;
        }

        if ($operation === 'site_audit_package_adapter_context') {
            $action_key = sanitize_key((string) ($payload['action_key'] ?? ''));
            wp_send_json_success(array(
                'adapter_context' => $this->aysa_technical_site_audit_build_package_adapter_context($project_id, $action_key, $payload),
                'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
            ));
            return;
        }

        if ($operation === 'site_audit_action_lifecycle') {
            $action_key = sanitize_key((string) ($payload['action_key'] ?? ''));
            $stage = sanitize_key((string) ($payload['stage'] ?? ''));
            $sync = $this->aysa_technical_site_audit_update_action_lifecycle($project_id, $action_key, $stage, $payload);
            if (empty($sync['applied'])) {
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                    'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
                'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
            ));
            return;
        }

        if ($operation === 'site_audit_metadata_remediation_dispatch') {
            $sync = $this->aysa_technical_site_audit_dispatch_metadata_remediation($project_id, $payload);
            if (empty($sync['applied'])) {
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                    'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
                'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
            ));
            return;
        }

        if ($operation === 'site_audit_metadata_remediation_apply') {
            $sync = $this->aysa_technical_site_audit_apply_metadata_remediation($project_id, $payload);
            if (empty($sync['applied'])) {
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                    'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
                'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
            ));
            return;
        }

        if ($operation === 'site_audit_image_alt_generation_dispatch') {
            $sync = $this->aysa_technical_site_audit_dispatch_image_alt_generation($project_id, $payload);
            if (empty($sync['applied'])) {
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                    'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
                'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
            ));
            return;
        }

        if ($operation === 'site_audit_image_inventory_scan') {
            $sync = $this->aysa_image_optimization_scan_inventory($project_id, $payload);
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
                'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
            ));
            return;
        }

        if ($operation === 'technical_image_alt_apply_bridge') {
            $sync = $this->aysa_agent8v3_technical_image_alt_apply_bridge($project_id, $payload);
            if (in_array((string) ($sync['status'] ?? ''), array('failed', 'auth_mismatch', 'idempotency_conflict'), true)) {
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'technical_redirect_dry_run_bridge') {
            $sync = $this->aysa_agent8v3_technical_redirect_dry_run_bridge($project_id, $payload);
            if (in_array((string) ($sync['status'] ?? ''), array('failed', 'auth_mismatch', 'idempotency_conflict', 'dry_run_blocked'), true)) {
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'technical_redirect_apply_bridge') {
            $sync = $this->aysa_agent8v3_technical_redirect_apply_bridge($project_id, $payload);
            if (in_array((string) ($sync['status'] ?? ''), array('failed', 'blocked', 'auth_mismatch', 'idempotency_conflict'), true)) {
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'technical_redirect_restore_bridge') {
            $sync = $this->aysa_agent8v3_technical_redirect_restore_bridge($project_id, $payload);
            if (in_array((string) ($sync['status'] ?? ''), array('restored', 'already_restored', 'rule_not_found', 'no_active_rule_to_restore'), true)) {
                wp_send_json_success(array(
                    'local_projection_sync' => $sync,
                ));
                return;
            }
            wp_send_json_error(array(
                'local_projection_sync' => $sync,
            ), 422);
            return;
        }

        if ($operation === 'technical_redirect_reverify_bridge') {
            $sync = $this->aysa_agent8v3_technical_redirect_reverify_bridge($project_id, $payload);
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'technical_metadata_apply_bridge') {
            $sync = $this->aysa_agent8v3_technical_metadata_apply_bridge($project_id, $payload);
            if (in_array((string) ($sync['status'] ?? ''), array('failed', 'auth_mismatch', 'idempotency_conflict'), true)) {
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'technical_metadata_restore_bridge') {
            $sync = $this->aysa_agent8v3_technical_metadata_restore_bridge($project_id, $payload);
            if (in_array((string) ($sync['status'] ?? ''), array('failed', 'auth_mismatch', 'idempotency_conflict'), true)) {
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'technical_canonical_apply_bridge') {
            $sync = $this->aysa_agent8v3_technical_canonical_apply_bridge($project_id, $payload);
            if (in_array((string) ($sync['status'] ?? ''), array('failed', 'auth_mismatch', 'idempotency_conflict'), true)) {
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'technical_canonical_restore_bridge') {
            $sync = $this->aysa_agent8v3_technical_canonical_restore_bridge($project_id, $payload);
            if (in_array((string) ($sync['status'] ?? ''), array('failed', 'auth_mismatch', 'idempotency_conflict'), true)) {
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'technical_schema_apply_bridge') {
            $sync = $this->aysa_agent8v3_technical_schema_apply_bridge($project_id, $payload);
            if (in_array((string) ($sync['status'] ?? ''), array('failed', 'auth_mismatch', 'idempotency_conflict'), true)) {
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'technical_schema_restore_bridge') {
            $sync = $this->aysa_agent8v3_technical_schema_restore_bridge($project_id, $payload);
            if (in_array((string) ($sync['status'] ?? ''), array('failed', 'auth_mismatch', 'idempotency_conflict'), true)) {
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'technical_image_optimization_apply_bridge') {
            $sync = $this->aysa_agent8v3_technical_image_optimization_apply_bridge($project_id, $payload);
            if (in_array((string) ($sync['status'] ?? ''), array('failed', 'auth_mismatch', 'idempotency_conflict'), true)) {
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'technical_image_optimization_restore_bridge') {
            $sync = $this->aysa_agent8v3_technical_image_optimization_restore_bridge($project_id, $payload);
            if (in_array((string) ($sync['status'] ?? ''), array('failed', 'auth_mismatch', 'idempotency_conflict'), true)) {
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'internal_linking_apply_bridge') {
            $sync = $this->aysa_agent8v3_internal_linking_apply_bridge($project_id, $payload);
            if (in_array((string) ($sync['status'] ?? ''), array('failed', 'auth_mismatch', 'idempotency_conflict'), true)) {
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'internal_linking_restore_bridge') {
            $sync = $this->aysa_agent8v3_internal_linking_restore_bridge($project_id, $payload);
            if (in_array((string) ($sync['status'] ?? ''), array('failed', 'auth_mismatch', 'idempotency_conflict'), true)) {
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'site_audit_image_optimization_batch_apply') {
            $sync = $this->aysa_image_optimization_apply_inventory_batch($project_id, $payload);
            if (empty($sync['applied'])) {
                if (function_exists('aysa_report_operational_error')) {
                    aysa_report_operational_error(array(
                        'source' => 'plugin',
                        'module' => 'technical',
                        'severity' => 'warning',
                        'error_code' => 'image_optimization_batch_apply_failed',
                        'message_safe' => 'Image optimization batch apply did not complete.',
                        'project_id' => $project_id,
                        'action_key' => $operation,
                        'technical_context' => array(
                            'operation' => $operation,
                            'reason' => $sync['reason'] ?? '',
                            'pending_count' => $sync['pending_count'] ?? null,
                            'failed_count' => $sync['failed_count'] ?? null,
                            'inventory_summary' => $sync['inventory_summary'] ?? array(),
                        ),
                    ));
                }
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                    'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
                'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
            ));
            return;
        }

        if ($operation === 'site_audit_image_optimization_single_apply') {
            $sync = $this->aysa_image_optimization_apply_single($project_id, $payload);
            if (empty($sync['applied'])) {
                if (function_exists('aysa_report_operational_error')) {
                    aysa_report_operational_error(array(
                        'source' => 'plugin',
                        'module' => 'technical',
                        'severity' => 'warning',
                        'error_code' => 'image_optimization_single_apply_failed',
                        'message_safe' => 'Single image optimization apply did not complete.',
                        'project_id' => $project_id,
                        'action_key' => $operation,
                        'technical_context' => array(
                            'operation' => $operation,
                            'attachment_id' => $sync['attachment_id'] ?? ($payload['attachment_id'] ?? 0),
                            'reason' => $sync['reason'] ?? '',
                        ),
                    ));
                }
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'site_audit_image_optimization_mark_failed') {
            $sync = $this->aysa_image_optimization_mark_inventory_failed($project_id, $payload);
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
                'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
            ));
            return;
        }

        if ($operation === 'site_audit_canonical_generation_dispatch') {
            $sync = $this->aysa_technical_site_audit_dispatch_canonical_generation($project_id, $payload);
            if (empty($sync['applied'])) {
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                    'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
                'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
            ));
            return;
        }

        if ($operation === 'site_audit_communication_event') {
            $event = $this->aysa_technical_site_audit_create_external_communication_event($project_id, $payload);
            if (empty($event['ok'])) {
                wp_send_json_error(array('communication_event' => $event), 422);
                return;
            }
            wp_send_json_success(array(
                'communication_event' => $event,
                'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
            ));
            return;
        }

        if ($operation === 'site_audit_shared_action_dispatch') {
            $sync = $this->aysa_technical_site_audit_dispatch_shared_action($project_id, $payload);
            if (empty($sync['applied'])) {
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                    'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
                'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
            ));
            return;
        }

        if (in_array($operation, array('site_audit_redirects_apply', 'site_audit_canonical_apply', 'site_audit_internal_linking_apply', 'site_audit_image_alt_apply'), true)) {
            $sync = $this->aysa_technical_site_audit_apply_shared_action($project_id, $payload);
            if (empty($sync['applied'])) {
                wp_send_json_error(array(
                    'local_projection_sync' => $sync,
                    'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
                ), 422);
                return;
            }
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
                'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
            ));
            return;
        }

        $run_id = intval($payload['run_id'] ?? 0);
        $projection = isset($payload['projection']) && is_array($payload['projection']) ? $payload['projection'] : array();
        if ($run_id <= 0 || $projection === array()) {
            wp_send_json_error(array('message' => 'Invalid projection sync payload'), 422);
            return;
        }

        $sync = $this->aysa_technical_site_audit_apply_local_projection($project_id, $run_id, $projection);
        wp_send_json_success(array(
            'local_projection_sync' => $sync,
        ));
    }

    public function handle_technical_pagespeed_projection_sync() {
        // Internal bearer auth is enforced in aysa_read_projection_sync_payload() via hash_equals().
        $payload = $this->aysa_read_projection_sync_payload();
        if (!is_array($payload)) {
            return;
        }

        $project_id = $this->aysa_resolve_projection_sync_project_id($payload);
        $operation = sanitize_key((string) ($payload['operation'] ?? ''));
        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Invalid project id'), 422);
            return;
        }

        if ($operation === 'preflight') {
            wp_send_json_success(array(
                'preflight' => array(
                    'status' => 'ready',
                    'endpoint' => 'technical_pagespeed',
                    'project_id' => $project_id,
                ),
            ));
            return;
        }

        if ($operation === 'reset_state') {
            $sync = $this->aysa_technical_pagespeed_reset_local_projection($project_id);
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        $run_id = intval($payload['run_id'] ?? 0);
        $projection = isset($payload['projection']) && is_array($payload['projection']) ? $payload['projection'] : array();
        if ($run_id <= 0 || $projection === array()) {
            wp_send_json_error(array('message' => 'Invalid projection sync payload'), 422);
            return;
        }

        $sync = $this->aysa_technical_pagespeed_apply_local_projection($project_id, $run_id, $projection);
        wp_send_json_success(array(
            'local_projection_sync' => $sync,
        ));
    }

    public function handle_technical_schema_projection_sync() {
        // Internal bearer auth is enforced in aysa_read_projection_sync_payload() via hash_equals().
        $payload = $this->aysa_read_projection_sync_payload();
        if (!is_array($payload)) {
            return;
        }

        $project_id = $this->aysa_resolve_projection_sync_project_id($payload);
        $operation = sanitize_key((string) ($payload['operation'] ?? ''));
        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Invalid project id'), 422);
            return;
        }

        if ($operation === 'preflight') {
            wp_send_json_success(array(
                'preflight' => array(
                    'status' => 'ready',
                    'endpoint' => 'technical_schema',
                    'project_id' => $project_id,
                ),
            ));
            return;
        }

        if ($operation === 'reset_state') {
            $sync = $this->aysa_technical_schema_reset_local_projection($project_id);
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'start_refresh') {
            $this->aysa_adjustments_delete_technical_schema_snapshot_cache($project_id);
            $running_job = $this->aysa_technical_schema_get_current_refresh_job($project_id);
            if ($running_job && in_array((string) ($running_job['status'] ?? ''), array('queued', 'running'), true)) {
                wp_send_json_success(array(
                    'job' => $this->aysa_technical_schema_get_job_response($running_job),
                    'started' => false,
                    'already_running' => true,
                ));
                return;
            }

            $items = array();
            foreach ((array) $this->aysa_adjustments_collect_schema_scope_pages() as $page_data) {
                if (!is_array($page_data)) {
                    continue;
                }
                $url = $this->aysa_adjustments_normalize_schema_url((string) ($page_data['url'] ?? ''));
                if ($url === '') {
                    continue;
                }
                $page_data['url'] = $url;
                $items[$url] = $page_data;
            }
            $items = array_values($items);
            if (empty($items)) {
                wp_send_json_error(array('message' => 'No schema scope pages were found for snapshot refresh.'), 422);
                return;
            }

            $job = $this->aysa_technical_schema_create_job($project_id, 'snapshot_refresh', 'sitewide', $items, 'snapshot_refresh');
            set_transient($this->aysa_technical_schema_refresh_job_pointer_key($project_id), (string) $job['job_id'], 12 * HOUR_IN_SECONDS);
            $summary_state = $this->aysa_adjustments_get_cached_or_idle_technical_schema_summary($project_id);
            $summary_state['status'] = 'rebuilding';
            $summary_state['needs_refresh'] = false;
            set_transient(
                $this->aysa_adjustments_get_schema_cache_key($project_id, 'summary'),
                $summary_state,
                HOUR_IN_SECONDS
            );
            $this->aysa_technical_schema_schedule_job((string) $job['job_id'], 1);

            wp_send_json_success(array(
                'job' => $this->aysa_technical_schema_get_job_response((array) $job),
                'started' => true,
            ));
            return;
        }

        if ($operation === 'poll_refresh') {
            $job_id = isset($payload['job_id']) ? sanitize_text_field((string) $payload['job_id']) : '';
            $batch_size = max(4, min(80, intval($payload['batch_size'] ?? 40)));
            $job = $job_id !== '' ? $this->aysa_technical_schema_read_job($job_id) : $this->aysa_technical_schema_get_current_refresh_job($project_id);

            if (!$job || intval($job['project_id'] ?? 0) !== $project_id) {
                wp_send_json_error(array('message' => 'Schema refresh job not found.'), 404);
                return;
            }

            if ((string) ($job['status'] ?? '') === 'queued') {
                $job = $this->aysa_technical_schema_process_job((string) ($job['job_id'] ?? ''), $batch_size);
            } elseif ((string) ($job['status'] ?? '') === 'running' && intval($job['lock_until'] ?? 0) <= time()) {
                $job = $this->aysa_technical_schema_process_job((string) ($job['job_id'] ?? ''), $batch_size);
            }

            wp_send_json_success(array(
                'job' => $this->aysa_technical_schema_get_job_response((array) $job),
            ));
            return;
        }

        if ($operation === 'final_snapshot') {
            $snapshot = $this->aysa_adjustments_get_technical_schema_snapshot($project_id, false);
            $markup = $this->aysa_adjustments_get_technical_schema_markup_snapshot($project_id);
            wp_send_json_success(array(
                'snapshot' => $snapshot,
                'markup' => $markup,
            ));
            return;
        }

        if ($operation === 'refresh_snapshot') {
            $snapshot = $this->aysa_adjustments_get_cached_or_idle_technical_schema_snapshot($project_id);
            $markup = $this->aysa_adjustments_get_technical_schema_markup_snapshot($project_id);
            $state = get_option($this->aysa_technical_schema_state_key($project_id), array());
            if (!is_array($state)) {
                $state = array();
            }
            $snapshot_has_data = !empty($snapshot['has_data']) || !empty($snapshot['coverage_rows']);
            $state = array_merge($state, array(
                'project_id' => $project_id,
                'updated_at' => current_time('mysql'),
                'updated_at_unix' => time(),
                'run_id' => intval($state['run_id'] ?? 0),
                'status' => $snapshot_has_data ? 'completed' : sanitize_key((string) ($snapshot['status'] ?? ($state['status'] ?? 'idle'))),
                'source' => 'worker_local_snapshot_cached',
                'orchestration' => 'worker_managed',
            ));
            update_option($this->aysa_technical_schema_state_key($project_id), $state, false);
            wp_send_json_success(array(
                'snapshot' => $snapshot,
                'markup' => $markup,
            ));
            return;
        }

        $run_id = intval($payload['run_id'] ?? 0);
        $projection = isset($payload['projection']) && is_array($payload['projection']) ? $payload['projection'] : array();
        if ($run_id <= 0 || $projection === array()) {
            wp_send_json_error(array('message' => 'Invalid projection sync payload'), 422);
            return;
        }

        $sync = $this->aysa_technical_schema_apply_local_projection($project_id, $run_id, $projection);
        wp_send_json_success(array(
            'local_projection_sync' => $sync,
        ));
    }

    public function handle_technical_redirects_projection_sync() {
        // Internal bearer auth is enforced in aysa_read_projection_sync_payload() via hash_equals().
        $payload = $this->aysa_read_projection_sync_payload();
        if (!is_array($payload)) {
            return;
        }

        $project_id = $this->aysa_resolve_projection_sync_project_id($payload);
        $operation = sanitize_key((string) ($payload['operation'] ?? ''));
        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Invalid project id'), 422);
            return;
        }

        if ($operation === 'preflight') {
            wp_send_json_success(array(
                'preflight' => array(
                    'status' => 'ready',
                    'endpoint' => 'technical_redirects',
                    'project_id' => $project_id,
                ),
            ));
            return;
        }

        if ($operation === 'reset_state') {
            $sync = $this->aysa_technical_redirects_reset_local_projection($project_id);
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'refresh_snapshot') {
            $snapshot = $this->aysa_adjustments_get_technical_errors_snapshot($project_id);
            $state = get_option($this->aysa_technical_redirects_state_key($project_id), array());
            if (!is_array($state)) {
                $state = array();
            }
            $state = array_merge($state, array(
                'project_id' => $project_id,
                'updated_at' => current_time('mysql'),
                'updated_at_unix' => time(),
                'run_id' => intval($state['run_id'] ?? 0),
                'status' => 'completed',
                'source' => 'worker_local_snapshot',
                'orchestration' => 'worker_managed',
                'snapshot' => $snapshot,
            ));
            update_option($this->aysa_technical_redirects_state_key($project_id), $state, false);
            wp_send_json_success(array(
                'snapshot' => $snapshot,
            ));
            return;
        }

        $run_id = intval($payload['run_id'] ?? 0);
        $projection = isset($payload['projection']) && is_array($payload['projection']) ? $payload['projection'] : array();
        if ($run_id <= 0 || $projection === array()) {
            wp_send_json_error(array('message' => 'Invalid projection sync payload'), 422);
            return;
        }

        $sync = $this->aysa_technical_redirects_apply_local_projection($project_id, $run_id, $projection);
        wp_send_json_success(array(
            'local_projection_sync' => $sync,
        ));
    }

    public function handle_technical_sitemap_robots_projection_sync() {
        // Internal bearer auth is enforced in aysa_read_projection_sync_payload() via hash_equals().
        $payload = $this->aysa_read_projection_sync_payload();
        if (!is_array($payload)) {
            return;
        }

        $project_id = $this->aysa_resolve_projection_sync_project_id($payload);
        $operation = sanitize_key((string) ($payload['operation'] ?? ''));
        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Invalid project id'), 422);
            return;
        }

        if ($operation === 'preflight') {
            wp_send_json_success(array(
                'preflight' => array(
                    'status' => 'ready',
                    'endpoint' => 'technical_sitemap_robots',
                    'project_id' => $project_id,
                ),
            ));
            return;
        }

        if ($operation === 'reset_state') {
            $sync = $this->aysa_technical_sitemap_robots_reset_local_projection($project_id);
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'technical_sitemap_authority_dry_run_bridge') {
            $sync = $this->aysa_technical_sitemap_authority_dry_run_bridge($project_id, $payload);
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'technical_sitemap_authority_restore_bridge') {
            $sync = $this->aysa_technical_sitemap_authority_restore_bridge($project_id, $payload);
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'technical_sitemap_authority_apply_bridge') {
            $sync = $this->aysa_technical_sitemap_authority_apply_bridge($project_id, $payload);
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'technical_sitemap_authority_post_takeover_readback') {
            $sync = $this->aysa_technical_sitemap_authority_post_takeover_readback_shell($project_id, $payload);
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'technical_sitemap_authority_staging_evidence_dry_run') {
            $sync = $this->aysa_technical_sitemap_authority_staging_evidence_dry_run_bridge($project_id, $payload);
            wp_send_json_success(array(
                'local_projection_sync' => $sync,
            ));
            return;
        }

        if ($operation === 'refresh_snapshot') {
            $target_url = isset($payload['target_url']) ? esc_url_raw((string) $payload['target_url']) : '';
            if ($target_url === '') {
                $target_url = $this->aysa_technical_resolve_target_url();
            }
            $audit = $this->aysa_technical_build_sitemap_robots_audit($target_url);
            $state = get_option($this->aysa_technical_sitemap_robots_state_key($project_id), array());
            if (!is_array($state)) {
                $state = array();
            }
            $state = array_merge($state, array(
                'project_id' => $project_id,
                'updated_at' => current_time('mysql'),
                'updated_at_unix' => time(),
                'data' => $audit,
                'target_url' => $target_url,
                'source' => 'worker_local_snapshot',
                'orchestration' => 'worker_managed',
            ));
            update_option($this->aysa_technical_sitemap_robots_state_key($project_id), $state, false);
            wp_send_json_success(array(
                'snapshot' => $this->aysa_adjustments_get_technical_sitemap_robots_snapshot($project_id),
            ));
            return;
        }

        $run_id = intval($payload['run_id'] ?? 0);
        $projection = isset($payload['projection']) && is_array($payload['projection']) ? $payload['projection'] : array();
        if ($run_id <= 0 || $projection === array()) {
            wp_send_json_error(array('message' => 'Invalid projection sync payload'), 422);
            return;
        }

        $sync = $this->aysa_technical_sitemap_robots_apply_local_projection($project_id, $run_id, $projection);
        wp_send_json_success(array(
            'local_projection_sync' => $sync,
        ));
    }

    private function aysa_get_bearer_authorization_token() {
        $header = '';
        if (!empty($_SERVER['HTTP_AUTHORIZATION'])) {
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
            $header = (string) wp_unslash($_SERVER['HTTP_AUTHORIZATION']);
        } elseif (!empty($_SERVER['Authorization'])) {
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
            $header = (string) wp_unslash($_SERVER['Authorization']);
        } elseif (function_exists('apache_request_headers')) {
            $headers = apache_request_headers();
            if (is_array($headers)) {
                foreach ($headers as $key => $value) {
                    if (strtolower((string) $key) === 'authorization') {
                        $header = (string) $value;
                        break;
                    }
                }
            }
        }

        if ($header === '' || stripos($header, 'Bearer ') !== 0) {
            return '';
        }

        return trim(substr($header, 7));
    }

    private function aysa_keyword_research_projection_applied_run_option_key($project_id) {
        return 'aysa_keyword_research_projection_applied_run_' . intval($project_id);
    }

    private function aysa_keyword_research_projection_last_content_row_count_option_key($project_id) {
        return 'aysa_keyword_research_projection_last_content_row_count_' . intval($project_id);
    }

    private function aysa_keyword_research_projection_last_calendar_row_count_option_key($project_id) {
        return 'aysa_keyword_research_projection_last_calendar_row_count_' . intval($project_id);
    }

    private function aysa_keyword_research_projection_last_applied_at_option_key($project_id) {
        return 'aysa_keyword_research_projection_last_applied_at_' . intval($project_id);
    }

    private function aysa_technical_site_audit_apply_local_projection($project_id, $run_id, $projection) {
        $project_id = $this->aysa_resolve_technical_project_id(intval($project_id));
        $run_id = intval($run_id);
        if ($project_id <= 0 || $run_id <= 0 || !is_array($projection)) {
            return array(
                'applied' => false,
                'reason' => 'invalid_projection_payload',
            );
        }

        $state_key = $this->aysa_technical_site_audit_state_key($project_id);
        $existing = get_option($state_key, array());
        if (!is_array($existing)) {
            $existing = array();
        }

        $summary = isset($projection['summary']) && is_array($projection['summary']) ? $projection['summary'] : array();
        $run = isset($projection['run']) && is_array($projection['run']) ? $projection['run'] : array();
        $snapshot = isset($projection['site_audit_snapshot']) && is_array($projection['site_audit_snapshot'])
            ? $projection['site_audit_snapshot']
            : array();
        $files = isset($projection['result_files']) && is_array($projection['result_files'])
            ? array_values($projection['result_files'])
            : array();
        $overview = !empty($snapshot['overview']) && is_array($snapshot['overview']) ? $snapshot['overview'] : array();
        $pages_crawled = intval($overview['pages_crawled'] ?? ($summary['pages_crawled'] ?? 0));
        $csv_file_count = 0;
        foreach ($files as $file) {
            if (!is_array($file)) {
                continue;
            }
            $filename = strtolower((string) ($file['filename'] ?? ''));
            $type = strtolower((string) ($file['type'] ?? ''));
            if ($type === 'csv' || substr($filename, -4) === '.csv') {
                $csv_file_count++;
            }
        }
        $is_valid_crawl = $pages_crawled > 0 && $csv_file_count > 0;
        $incoming_status = sanitize_key((string) ($run['status'] ?? ($summary['status'] ?? 'completed')));
        if (!$is_valid_crawl && in_array($incoming_status, array('completed', 'complete', 'success', 'succeeded', 'ready'), true)) {
            $incoming_status = 'invalid';
        }

        $state = array_merge($existing, array(
            'project_id' => $project_id,
            'run_id' => $run_id,
            'last_applied_run_id' => $run_id,
            'status' => $incoming_status,
            'valid_crawl' => $is_valid_crawl,
            'invalid_reason' => $is_valid_crawl ? '' : 'empty_or_missing_crawl_artifacts',
            'job_id' => sanitize_text_field((string) ($projection['job_id'] ?? ($existing['job_id'] ?? ''))),
            'target_url' => esc_url_raw((string) ($summary['target_url'] ?? ($existing['target_url'] ?? ''))),
            'started_at' => sanitize_text_field((string) ($existing['started_at'] ?? current_time('mysql'))),
            'updated_at' => current_time('mysql'),
            'provider' => 'aysa_site_audit',
            'source' => 'worker_projection_sync',
            'orchestration' => 'worker_managed',
            'current_task_key' => sanitize_key((string) ($run['current_task_key'] ?? '')),
            'current_milestone' => sanitize_text_field((string) ($run['current_milestone'] ?? '')),
            'audit_snapshot' => $snapshot,
            'result_files' => $files,
            'latest_output_dir' => sanitize_text_field((string) ($projection['latest_output_dir'] ?? ($existing['latest_output_dir'] ?? ''))),
            'last_applied_at' => current_time('mysql'),
        ));

        $version_id = 0;
        if (!empty($snapshot) || !empty($files)) {
            $version_id = intval($this->aysa_sf_site_audit_persist_version($project_id, $state));
            if ($version_id > 0) {
                $latest_version = $this->aysa_sf_site_audit_get_latest_version_row($project_id);
                if (is_array($latest_version) && !empty($latest_version)) {
                    $state['version_id'] = intval($latest_version['id'] ?? 0);
                    $state['version_number'] = intval($latest_version['version_number'] ?? 0);
                }
            }
        }

        update_option($state_key, $state, false);

        return array(
            'applied' => true,
            'run_id' => $run_id,
            'status' => (string) ($state['status'] ?? 'completed'),
            'valid_crawl' => !empty($state['valid_crawl']),
            'job_id' => (string) ($state['job_id'] ?? ''),
            'snapshot_version_id' => $version_id,
            'result_file_count' => count($files),
        );
    }

    private function aysa_technical_site_audit_reset_local_projection($project_id) {
        $project_id = $this->aysa_resolve_technical_project_id(intval($project_id));
        if ($project_id <= 0) {
            return array(
                'applied' => false,
                'reason' => 'invalid_project_id',
            );
        }

        update_option($this->aysa_technical_site_audit_state_key($project_id), array(
            'project_id' => $project_id,
            'run_id' => 0,
            'last_applied_run_id' => 0,
            'job_id' => '',
            'status' => 'idle',
            'source' => 'manual_reset',
            'orchestration' => 'worker_managed',
            'updated_at' => current_time('mysql'),
        ), false);

        return array(
            'applied' => true,
            'status' => 'idle',
        );
    }

    private function aysa_technical_pagespeed_apply_local_projection($project_id, $run_id, $projection) {
        $project_id = intval($project_id);
        $run_id = intval($run_id);
        if ($project_id <= 0 || $run_id <= 0 || !is_array($projection)) {
            return array(
                'applied' => false,
                'reason' => 'invalid_projection_payload',
            );
        }

        $state_key = $this->aysa_technical_pagespeed_state_key($project_id);
        $existing = get_option($state_key, array());
        if (!is_array($existing)) {
            $existing = array();
        }

        $summary = isset($projection['summary']) && is_array($projection['summary']) ? $projection['summary'] : array();
        $run = isset($projection['run']) && is_array($projection['run']) ? $projection['run'] : array();
        $snapshot = isset($projection['pagespeed_snapshot']) && is_array($projection['pagespeed_snapshot'])
            ? $projection['pagespeed_snapshot']
            : array();
        $raw = isset($snapshot['raw']) && is_array($snapshot['raw']) ? $snapshot['raw'] : array();
        $state_data = $snapshot;
        if ($state_data === array() && $raw !== array()) {
            $state_data = array('raw' => $raw);
        } elseif ($raw !== array()) {
            $state_data['raw'] = $raw;
        }

        $state = array_merge($existing, array(
            'project_id' => $project_id,
            'run_id' => $run_id,
            'last_applied_run_id' => $run_id,
            'status' => sanitize_key((string) ($run['status'] ?? ($summary['status'] ?? 'completed'))),
            'target_url' => esc_url_raw((string) ($summary['target_url'] ?? ($snapshot['url'] ?? ($existing['target_url'] ?? '')))),
            'started_at' => sanitize_text_field((string) ($existing['started_at'] ?? current_time('mysql'))),
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'provider' => 'google_pagespeed_api',
            'source' => 'worker_projection_sync',
            'orchestration' => 'worker_managed',
            'data' => $state_data,
        ));

        update_option($state_key, $state, false);

        return array(
            'applied' => true,
            'run_id' => $run_id,
            'status' => (string) ($state['status'] ?? 'completed'),
            'overall_score' => intval($summary['overall_score'] ?? 0),
        );
    }

    private function aysa_technical_pagespeed_reset_local_projection($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array(
                'applied' => false,
                'reason' => 'invalid_project_id',
            );
        }

        update_option($this->aysa_technical_pagespeed_state_key($project_id), array(
            'project_id' => $project_id,
            'run_id' => 0,
            'last_applied_run_id' => 0,
            'status' => 'idle',
            'target_url' => '',
            'source' => 'manual_reset',
            'orchestration' => 'worker_managed',
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'data' => array(),
        ), false);

        return array(
            'applied' => true,
            'status' => 'idle',
        );
    }

    private function aysa_technical_schema_apply_local_projection($project_id, $run_id, $projection) {
        $project_id = intval($project_id);
        $run_id = intval($run_id);
        if ($project_id <= 0 || $run_id <= 0 || !is_array($projection)) {
            return array(
                'applied' => false,
                'reason' => 'invalid_projection_payload',
            );
        }

        $state_key = $this->aysa_technical_schema_state_key($project_id);
        $existing = get_option($state_key, array());
        if (!is_array($existing)) {
            $existing = array();
        }

        $summary = isset($projection['summary']) && is_array($projection['summary']) ? $projection['summary'] : array();
        $run = isset($projection['run']) && is_array($projection['run']) ? $projection['run'] : array();
        $snapshot = isset($projection['schema_snapshot']) && is_array($projection['schema_snapshot'])
            ? $projection['schema_snapshot']
            : array();
        $markup = isset($projection['schema_markup_snapshot']) && is_array($projection['schema_markup_snapshot'])
            ? $projection['schema_markup_snapshot']
            : array();

        if ($snapshot !== array()) {
            $this->aysa_adjustments_write_technical_schema_snapshot_cache($project_id, $snapshot);
        }

        $state = array_merge($existing, array(
            'project_id' => $project_id,
            'run_id' => $run_id,
            'last_applied_run_id' => $run_id,
            'status' => sanitize_key((string) ($run['status'] ?? ($summary['status'] ?? 'completed'))),
            'started_at' => sanitize_text_field((string) ($existing['started_at'] ?? current_time('mysql'))),
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'provider' => 'wp_local_snapshot',
            'source' => 'worker_projection_sync',
            'orchestration' => 'worker_managed',
            'schema_markup_snapshot' => $markup,
        ));

        update_option($state_key, $state, false);

        return array(
            'applied' => true,
            'run_id' => $run_id,
            'status' => (string) ($state['status'] ?? 'completed'),
            'total_pages' => intval($summary['total_pages'] ?? 0),
        );
    }

    private function aysa_technical_schema_reset_local_projection($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array(
                'applied' => false,
                'reason' => 'invalid_project_id',
            );
        }

        update_option($this->aysa_technical_schema_state_key($project_id), array(
            'project_id' => $project_id,
            'run_id' => 0,
            'last_applied_run_id' => 0,
            'status' => 'idle',
            'source' => 'manual_reset',
            'orchestration' => 'worker_managed',
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'schema_markup_snapshot' => array(),
        ), false);
        $this->aysa_adjustments_delete_technical_schema_snapshot_cache($project_id);

        return array(
            'applied' => true,
            'status' => 'idle',
        );
    }

    private function aysa_technical_redirects_apply_local_projection($project_id, $run_id, $projection) {
        $project_id = intval($project_id);
        $run_id = intval($run_id);
        if ($project_id <= 0 || $run_id <= 0 || !is_array($projection)) {
            return array(
                'applied' => false,
                'reason' => 'invalid_projection_payload',
            );
        }

        $state_key = $this->aysa_technical_redirects_state_key($project_id);
        $existing = get_option($state_key, array());
        if (!is_array($existing)) {
            $existing = array();
        }

        $summary = isset($projection['summary']) && is_array($projection['summary']) ? $projection['summary'] : array();
        $run = isset($projection['run']) && is_array($projection['run']) ? $projection['run'] : array();
        $snapshot = isset($projection['technical_errors_snapshot']) && is_array($projection['technical_errors_snapshot'])
            ? $projection['technical_errors_snapshot']
            : array();

        $state = array_merge($existing, array(
            'project_id' => $project_id,
            'run_id' => $run_id,
            'last_applied_run_id' => $run_id,
            'status' => sanitize_key((string) ($run['status'] ?? ($summary['status'] ?? 'completed'))),
            'started_at' => sanitize_text_field((string) ($existing['started_at'] ?? current_time('mysql'))),
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'provider' => 'wp_local_snapshot',
            'source' => 'worker_projection_sync',
            'orchestration' => 'worker_managed',
            'snapshot' => $snapshot,
        ));

        update_option($state_key, $state, false);

        return array(
            'applied' => true,
            'run_id' => $run_id,
            'status' => (string) ($state['status'] ?? 'completed'),
            'total_404' => intval($summary['total_404'] ?? 0),
        );
    }

    private function aysa_technical_redirects_reset_local_projection($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array(
                'applied' => false,
                'reason' => 'invalid_project_id',
            );
        }

        update_option($this->aysa_technical_redirects_state_key($project_id), array(
            'project_id' => $project_id,
            'run_id' => 0,
            'last_applied_run_id' => 0,
            'status' => 'idle',
            'source' => 'manual_reset',
            'orchestration' => 'worker_managed',
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'snapshot' => array(),
        ), false);

        return array(
            'applied' => true,
            'status' => 'idle',
        );
    }

    private function aysa_technical_sitemap_robots_apply_local_projection($project_id, $run_id, $projection) {
        $project_id = intval($project_id);
        $run_id = intval($run_id);
        if ($project_id <= 0 || $run_id <= 0 || !is_array($projection)) {
            return array(
                'applied' => false,
                'reason' => 'invalid_projection_payload',
            );
        }

        $state_key = $this->aysa_technical_sitemap_robots_state_key($project_id);
        $existing = get_option($state_key, array());
        if (!is_array($existing)) {
            $existing = array();
        }

        $summary = isset($projection['summary']) && is_array($projection['summary']) ? $projection['summary'] : array();
        $run = isset($projection['run']) && is_array($projection['run']) ? $projection['run'] : array();
        $snapshot = isset($projection['sitemap_robots_snapshot']) && is_array($projection['sitemap_robots_snapshot'])
            ? $projection['sitemap_robots_snapshot']
            : array();
        $target_url = esc_url_raw((string) ($summary['target_url'] ?? ($snapshot['target_url'] ?? ($existing['target_url'] ?? ''))));
        $raw_data = isset($existing['data']) && is_array($existing['data']) ? $existing['data'] : array();
        if ($snapshot !== array() && $raw_data === array()) {
            $raw_data = $this->aysa_technical_sitemap_robots_snapshot_to_raw_data($snapshot, $target_url);
        }

        $state = array_merge($existing, array(
            'project_id' => $project_id,
            'run_id' => $run_id,
            'last_applied_run_id' => $run_id,
            'status' => sanitize_key((string) ($run['status'] ?? ($summary['status'] ?? 'completed'))),
            'target_url' => $target_url,
            'started_at' => sanitize_text_field((string) ($existing['started_at'] ?? current_time('mysql'))),
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'provider' => 'wp_local_snapshot',
            'source' => 'worker_projection_sync',
            'orchestration' => 'worker_managed',
            'data' => $raw_data,
        ));

        update_option($state_key, $state, false);

        return array(
            'applied' => true,
            'run_id' => $run_id,
            'status' => (string) ($state['status'] ?? 'completed'),
            'checks' => intval($summary['checks'] ?? 0),
        );
    }

    private function aysa_technical_sitemap_robots_reset_local_projection($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array(
                'applied' => false,
                'reason' => 'invalid_project_id',
            );
        }

        update_option($this->aysa_technical_sitemap_robots_state_key($project_id), array(
            'project_id' => $project_id,
            'run_id' => 0,
            'last_applied_run_id' => 0,
            'status' => 'idle',
            'target_url' => '',
            'source' => 'manual_reset',
            'orchestration' => 'worker_managed',
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'data' => array(),
        ), false);

        return array(
            'applied' => true,
            'status' => 'idle',
        );
    }

    private function aysa_technical_sitemap_robots_snapshot_to_raw_data($snapshot, $target_url = '') {
        $snapshot = is_array($snapshot) ? $snapshot : array();
        return array(
            'target_url' => $target_url !== '' ? $target_url : esc_url_raw((string) ($snapshot['target_url'] ?? '')),
            'primary_sitemap' => esc_url_raw((string) ($snapshot['primary_sitemap'] ?? '')),
            'summary' => array(
                'errors' => intval($snapshot['errors'] ?? 0),
                'warnings' => intval($snapshot['warnings'] ?? 0),
                'passes' => intval($snapshot['passes'] ?? 0),
                'robots_health' => sanitize_text_field((string) ($snapshot['robots_health'] ?? '')),
                'sitemap_health' => sanitize_text_field((string) ($snapshot['sitemap_health'] ?? '')),
            ),
            'checks' => isset($snapshot['rows']) && is_array($snapshot['rows']) ? array_values($snapshot['rows']) : array(),
            'sitemaps' => isset($snapshot['sitemaps']) && is_array($snapshot['sitemaps']) ? array_values($snapshot['sitemaps']) : array(),
            'robots' => array(
                'source_label' => isset($snapshot['robots_rows'][0]['value']) ? sanitize_text_field((string) $snapshot['robots_rows'][0]['value']) : '',
                'content' => isset($snapshot['robots_rows'][2]['value']) ? sanitize_textarea_field((string) $snapshot['robots_rows'][2]['value']) : '',
            ),
        );
    }

    private function aysa_keyword_research_apply_local_projection($project_id, $run_id, $projection) {
        $project_id = intval($project_id);
        $run_id = intval($run_id);
        if ($project_id <= 0 || $run_id <= 0 || !is_array($projection)) {
            return array(
                'applied' => false,
                'reason' => 'invalid_projection_payload',
            );
        }

        $last_applied_run = intval(get_option($this->aysa_keyword_research_projection_applied_run_option_key($project_id), 0));
        $requires_reapply = $last_applied_run === $run_id
            && $this->aysa_keyword_research_projection_requires_reapply($project_id, $run_id, $projection);
        if ($last_applied_run > $run_id || ($last_applied_run === $run_id && !$requires_reapply)) {
            return array(
                'applied' => false,
                'already_applied' => true,
                'run_id' => $last_applied_run,
            );
        }

        $keyword_snapshot = isset($projection['keyword_snapshot']) && is_array($projection['keyword_snapshot'])
            ? $projection['keyword_snapshot']
            : array();
        $keywords = isset($keyword_snapshot['keywords']) && is_array($keyword_snapshot['keywords'])
            ? $keyword_snapshot['keywords']
            : array();
        $persisted_keywords = $this->aysa_keyword_discovery_persist_keywords($project_id, $keywords);
        update_option($this->aysa_keyword_discovery_snapshot_key($project_id), array(
            'run_id' => (string) ($keyword_snapshot['run_id'] ?? $run_id),
            'project_id' => $project_id,
            'fetched_at' => intval($keyword_snapshot['fetched_at'] ?? time()),
            'count' => intval($keyword_snapshot['count'] ?? count($keywords)),
            'keywords' => $keywords,
        ), false);
        $this->aysa_keyword_discovery_save_status($project_id, array(
            'run_id' => (string) $run_id,
            'refresh_kind' => 'gsc_auto',
            'status' => 'completed',
            'total' => intval($keyword_snapshot['count'] ?? count($keywords)),
            'processed' => intval($keyword_snapshot['count'] ?? count($keywords)),
            'staged' => 0,
            'inserted' => intval($persisted_keywords['inserted'] ?? 0),
            'updated' => intval($persisted_keywords['updated'] ?? 0),
            'apply_to_wp' => 1,
            'last_error' => '',
            'metrics_notice' => '',
            'notification_sent' => '0',
            'started_at' => time(),
            'updated_at' => time(),
            'finished_at' => time(),
        ));

        $association_rows = $this->aysa_keyword_research_projection_build_local_associations(
            $project_id,
            isset($projection['local_associations']) && is_array($projection['local_associations'])
                ? $projection['local_associations']
                : array()
        );
        if (!empty($association_rows)) {
            $this->save_keyword_discovery_local_associations($project_id, $association_rows);
        }

        if (isset($projection['competitor_identification']) && is_array($projection['competitor_identification'])) {
            update_option($this->aysa_competitor_identification_cache_key($project_id), $projection['competitor_identification'], false);
        }
        if (isset($projection['competitor_gap']) && is_array($projection['competitor_gap'])) {
            update_option($this->aysa_competitor_gap_cache_key($project_id), $projection['competitor_gap'], false);
        }
        if (isset($projection['backlink_summary']) && is_array($projection['backlink_summary'])) {
            update_option('aysa_backlink_summary_' . $project_id, $projection['backlink_summary'], false);
        }

        $content_strategy_result = $this->aysa_keyword_research_projection_apply_content_strategy_rows(
            $project_id,
            $run_id,
            isset($projection['content_strategy_rows']) && is_array($projection['content_strategy_rows'])
                ? $projection['content_strategy_rows']
                : array()
        );

        update_option($this->aysa_keyword_research_projection_applied_run_option_key($project_id), $run_id, false);
        update_option(
            $this->aysa_keyword_research_projection_last_content_row_count_option_key($project_id),
            intval($content_strategy_result['saved'] ?? 0),
            false
        );
        update_option(
            $this->aysa_keyword_research_projection_last_calendar_row_count_option_key($project_id),
            count(isset($projection['publishing_calendar_rows']) && is_array($projection['publishing_calendar_rows']) ? $projection['publishing_calendar_rows'] : array()),
            false
        );
        update_option(
            $this->aysa_keyword_research_projection_last_applied_at_option_key($project_id),
            current_time('mysql'),
            false
        );

        return array(
            'applied' => true,
            'run_id' => $run_id,
            'keywords_total' => intval($keyword_snapshot['count'] ?? count($keywords)),
            'associations_total' => count($association_rows),
            'content_strategy_saved' => intval($content_strategy_result['saved'] ?? 0),
            'calendar_rows' => count(isset($projection['publishing_calendar_rows']) && is_array($projection['publishing_calendar_rows']) ? $projection['publishing_calendar_rows'] : array()),
            'reapplied' => $requires_reapply,
        );
    }

    private function aysa_keyword_research_projection_requires_reapply($project_id, $run_id, $projection) {
        global $wpdb;

        $project_id = intval($project_id);
        $run_id = intval($run_id);
        if ($project_id <= 0 || $run_id <= 0 || !is_array($projection)) {
            return false;
        }

        $association_rows = isset($projection['local_associations']) && is_array($projection['local_associations'])
            ? $projection['local_associations']
            : array();
        if ($association_rows !== array()) {
            $projected_associations = $this->aysa_keyword_research_projection_build_local_associations($project_id, $association_rows);
            $existing_associations = $this->get_keyword_discovery_local_associations($project_id);
            if (count($projected_associations) !== count($existing_associations)) {
                return true;
            }
            $projected_signature = array_map(static function ($row) {
                return implode('|', array(
                    (string) ($row['keyword'] ?? ''),
                    (string) ($row['associatedOn'] ?? ''),
                    (string) ($row['idIntern'] ?? ''),
                    (string) intval($row['isMain'] ?? 0),
                    (string) ($row['secondKeyword'] ?? ''),
                ));
            }, $projected_associations);
            $existing_signature = array_map(static function ($row) {
                return implode('|', array(
                    (string) ($row['keyword'] ?? ''),
                    (string) ($row['associatedOn'] ?? ''),
                    (string) ($row['idIntern'] ?? ''),
                    (string) intval($row['isMain'] ?? 0),
                    (string) ($row['secondKeyword'] ?? ''),
                ));
            }, $existing_associations);
            sort($projected_signature);
            sort($existing_signature);
            if ($projected_signature !== $existing_signature) {
                return true;
            }
        }

        $content_rows = isset($projection['content_strategy_rows']) && is_array($projection['content_strategy_rows'])
            ? $projection['content_strategy_rows']
            : array();
        if ($content_rows === array()) {
            return false;
        }

        $table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if ($exists !== $table) {
            return false;
        }

        $plan_batch = 'keyword_research_run_' . $run_id;
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $existing_count = intval($wpdb->get_var($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT COUNT(*)
" .
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
            "             FROM {$table}
             WHERE project_id = %d
               AND plan_batch = %s
               AND COALESCE(automation_status, '') <> 'deleted'
               AND duplicate_of_strategy_id IS NULL
               AND superseded_by_strategy_id IS NULL",
            $project_id,
            $plan_batch
        )));

        if ($existing_count === 0) {
            return true;
        }

        if ($existing_count !== count($content_rows)) {
            return true;
        }

        $projected_types = array();
        foreach ($content_rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $type = sanitize_key((string) ($row['strategy_type'] ?? $row['strategy_module'] ?? 'keyword_research'));
            if ($type !== '') {
                $projected_types[$type] = true;
            }
        }
        $projected_type_keys = array_keys($projected_types);
        sort($projected_type_keys);

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $existing_types = $wpdb->get_col($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT DISTINCT strategy_type
" .
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
            "             FROM {$table}
             WHERE project_id = %d
               AND plan_batch = %s
               AND COALESCE(automation_status, '') <> 'deleted'
               AND duplicate_of_strategy_id IS NULL
               AND superseded_by_strategy_id IS NULL",
            $project_id,
            $plan_batch
        ));
        $existing_type_keys = array_values(array_filter(array_map('sanitize_key', is_array($existing_types) ? $existing_types : array())));
        sort($existing_type_keys);

        return $projected_type_keys !== $existing_type_keys;
    }

    private function aysa_keyword_research_projection_build_local_associations($project_id, $rows) {
        $project_id = intval($project_id);
        if ($project_id <= 0 || !is_array($rows)) {
            return array();
        }

        $normalized = array();
        $safe_secondaries_by_entity = array();
        $secondary_count_by_entity = array();
        $safe_primary_by_entity = $this->aysa_keyword_mapping_execution_choose_primaries($rows);
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keyword = trim((string) ($row['keyword'] ?? ''));
            $target_url = trim((string) ($row['targetUrl'] ?? $row['target_url'] ?? ''));
            if ($keyword === '' || $target_url === '') {
                continue;
            }

            $target = $this->aysa_keyword_research_projection_resolve_target_by_url($target_url);
            if (!is_array($target) || empty($target['associated_on']) || intval($target['id_intern'] ?? 0) <= 0) {
                continue;
            }

            $associated_on = (string) $target['associated_on'];
            $id_intern = intval($target['id_intern']);
            $value = trim((string) ($target['value'] ?? ($row['entityLabel'] ?? $target_url)));
            if ($value === '') {
                $value = $target_url;
            }

            $entity_key = trim((string) ($row['entityKey'] ?? $row['entity_key'] ?? $target_url));
            if ($entity_key === '') {
                $entity_key = $target_url;
            }
            $raw_is_main = !empty($row['isMain']) || !empty($row['is_main']);
            $safe_primary = isset($safe_primary_by_entity[$entity_key]) && is_array($safe_primary_by_entity[$entity_key])
                ? $safe_primary_by_entity[$entity_key]
                : array();
            $safe_primary_keyword = trim((string) ($safe_primary['keyword'] ?? ''));
            $is_main = $safe_primary_keyword !== ''
                ? strcasecmp($keyword, $safe_primary_keyword) === 0
                : $raw_is_main;
            if (!$is_main) {
                if (!$this->aysa_keyword_mapping_execution_guard_allows_secondary($keyword, $target_url, $value, $associated_on)) {
                    continue;
                }
                $limit = $this->aysa_keyword_mapping_execution_secondary_limit($target_url, $associated_on);
                $current_count = intval($secondary_count_by_entity[$entity_key] ?? 0);
                if ($current_count >= $limit) {
                    continue;
                }
                $secondary_count_by_entity[$entity_key] = $current_count + 1;
                if (!isset($safe_secondaries_by_entity[$entity_key])) {
                    $safe_secondaries_by_entity[$entity_key] = array();
                }
                $safe_secondaries_by_entity[$entity_key][] = $keyword;
            }

            $normalized[] = array(
                'id' => $this->build_keyword_discovery_stable_local_id($keyword, $associated_on, $id_intern),
                'projectId' => $project_id,
                'keyword' => $keyword,
                'associatedOn' => $associated_on,
                'idIntern' => $id_intern,
                'value' => $value,
                'desiredPosition' => null,
                'source' => 'local_fallback',
                'isMain' => $is_main ? 1 : 0,
                'secondKeyword' => trim((string) ($row['secondKeyword'] ?? $row['second_keyword'] ?? '')),
                'secondaryKeywords' => array(),
                'entityKey' => $entity_key,
                'mappingSource' => trim((string) ($row['mapping_source'] ?? $row['mappingSource'] ?? '')),
                'confidenceScore' => isset($row['confidence_score']) && is_numeric($row['confidence_score']) ? floatval($row['confidence_score']) : (isset($row['score']) && is_numeric($row['score']) ? floatval($row['score']) : null),
                'intentType' => trim((string) ($row['intent_type'] ?? $row['intentType'] ?? '')),
                'targetUrl' => $target_url,
                'executionPrimaryKeyword' => $safe_primary_keyword,
                'executionPrimarySource' => trim((string) ($safe_primary['source'] ?? 'raw_mapping_primary')),
                'executionPrimaryConfidence' => isset($safe_primary['confidence']) && is_numeric($safe_primary['confidence']) ? floatval($safe_primary['confidence']) : null,
                'executionPrimaryReason' => trim((string) ($safe_primary['reason'] ?? '')),
                'rawIsMain' => $raw_is_main ? 1 : 0,
                'updatedAt' => current_time('mysql'),
            );
        }

        foreach ($normalized as &$association) {
            if (empty($association['isMain'])) {
                continue;
            }
            $entity_key = trim((string) ($association['entityKey'] ?? ''));
            $association['secondaryKeywords'] = isset($safe_secondaries_by_entity[$entity_key])
                ? array_values($safe_secondaries_by_entity[$entity_key])
                : array();
        }
        unset($association);

        return $normalized;
    }

    private function aysa_keyword_mapping_execution_choose_primaries($rows) {
        $groups = array();
        foreach ((array) $rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keyword = trim((string) ($row['keyword'] ?? ''));
            $target_url = trim((string) ($row['targetUrl'] ?? $row['target_url'] ?? ''));
            if ($keyword === '' || $target_url === '') {
                continue;
            }
            $entity_key = trim((string) ($row['entityKey'] ?? $row['entity_key'] ?? $target_url));
            if ($entity_key === '') {
                $entity_key = $target_url;
            }
            if (!isset($groups[$entity_key])) {
                $groups[$entity_key] = array(
                    'target_url' => $target_url,
                    'label' => trim((string) ($row['entityLabel'] ?? $target_url)),
                    'rows' => array(),
                );
            }
            $groups[$entity_key]['rows'][] = $row;
        }

        $chosen = array();
        foreach ($groups as $entity_key => $group) {
            $best = null;
            $best_score = -9999;
            foreach ((array) ($group['rows'] ?? array()) as $row) {
                $score = $this->aysa_keyword_mapping_execution_primary_candidate_score(
                    $row,
                    (string) ($group['target_url'] ?? ''),
                    (string) ($group['label'] ?? '')
                );
                if ($score === null) {
                    continue;
                }
                if ($best === null || $score > $best_score) {
                    $best = $row;
                    $best_score = $score;
                }
            }
            if ($best === null) {
                foreach ((array) ($group['rows'] ?? array()) as $row) {
                    if (!empty($row['isMain']) || !empty($row['is_main'])) {
                        $best = $row;
                        $best_score = isset($row['confidence_score']) && is_numeric($row['confidence_score'])
                            ? floatval($row['confidence_score'])
                            : (isset($row['score']) && is_numeric($row['score']) ? floatval($row['score']) : 0);
                        break;
                    }
                }
            }
            if ($best !== null) {
                $chosen[$entity_key] = array(
                    'keyword' => trim((string) ($best['keyword'] ?? '')),
                    'source' => trim((string) ($best['mapping_source'] ?? $best['mappingSource'] ?? '')),
                    'confidence' => round(floatval($best_score), 4),
                    'reason' => $this->aysa_keyword_mapping_execution_primary_reason($best, (string) ($group['target_url'] ?? ''), (string) ($group['label'] ?? '')),
                );
            }
        }

        return $chosen;
    }

    private function aysa_keyword_mapping_execution_primary_candidate_score($row, $target_url, $entity_label) {
        $keyword = trim((string) ($row['keyword'] ?? ''));
        if ($keyword === '') {
            return null;
        }
        $keyword_norm = $this->aysa_keyword_mapping_execution_normalize_text($keyword);
        $label_norm = $this->aysa_keyword_mapping_execution_normalize_text($entity_label . ' ' . $target_url);
        if ($this->aysa_keyword_mapping_execution_is_noise_keyword($keyword_norm)) {
            return null;
        }

        $score = isset($row['confidence_score']) && is_numeric($row['confidence_score'])
            ? floatval($row['confidence_score'])
            : (isset($row['score']) && is_numeric($row['score']) ? floatval($row['score']) : 0);
        if (!empty($row['isMain']) || !empty($row['is_main'])) {
            $score += 0.04;
        }
        if (trim((string) ($row['mapping_source'] ?? $row['mappingSource'] ?? '')) === 'gsc') {
            $score += 0.08;
        }

        if ($this->aysa_keyword_mapping_execution_is_homepage_url($target_url)) {
            if (!$this->aysa_keyword_mapping_execution_has_commercial_intent($keyword_norm)) {
                return null;
            }
            if (preg_match('/\bbragadiru\b/u', $keyword_norm)) {
                $score += 0.45;
            }
            if (preg_match('/\bilfov\b/u', $keyword_norm)) {
                $score += 0.25;
            }
            if (preg_match('/\bbucuresti\b/u', $keyword_norm) && !preg_match('/\b(bragadiru|ilfov)\b/u', $keyword_norm)) {
                $score -= 0.18;
            }
            if (preg_match('/\b(buchet|buchete|bonsai|bujori|trandafiri|lalele|frezii|ghiveci|ursuleti|primavara|martie|nastere)\b/u', $keyword_norm)) {
                $score -= 0.5;
            }
            return $score;
        }

        if ($this->aysa_keyword_mapping_execution_is_product_target($target_url, '', $entity_label)) {
            if ($this->aysa_keyword_mapping_execution_has_informational_intent($keyword_norm)) {
                return null;
            }
            if ($this->aysa_keyword_mapping_execution_has_quantity_mismatch($keyword_norm, $label_norm)) {
                return null;
            }
            if ($this->aysa_keyword_mapping_execution_is_broad_category_keyword($keyword_norm, $label_norm)) {
                $score -= 0.45;
            }
            preg_match_all('/\b\d+\b/u', $label_norm, $label_numbers);
            foreach (array_unique($label_numbers[0] ?? array()) as $number) {
                if (preg_match('/\b' . preg_quote((string) $number, '/') . '\b/u', $keyword_norm)) {
                    $score += 0.45;
                }
            }
        }

        return $score;
    }

    private function aysa_keyword_mapping_execution_primary_reason($row, $target_url, $entity_label) {
        $keyword_norm = $this->aysa_keyword_mapping_execution_normalize_text((string) ($row['keyword'] ?? ''));
        $label_norm = $this->aysa_keyword_mapping_execution_normalize_text($entity_label . ' ' . $target_url);
        if ($this->aysa_keyword_mapping_execution_is_homepage_url($target_url) && preg_match('/\b(bragadiru|ilfov)\b/u', $keyword_norm)) {
            return 'preferred_local_homepage_intent';
        }
        if ($this->aysa_keyword_mapping_execution_is_product_target($target_url, '', $entity_label)) {
            preg_match_all('/\b\d+\b/u', $label_norm, $label_numbers);
            foreach (array_unique($label_numbers[0] ?? array()) as $number) {
                if (preg_match('/\b' . preg_quote((string) $number, '/') . '\b/u', $keyword_norm)) {
                    return 'preferred_product_specific_quantity_match';
                }
            }
            return 'preferred_product_specific_fit';
        }
        return 'preferred_highest_safe_score';
    }

    private function aysa_keyword_mapping_execution_guard_allows_secondary($keyword, $target_url, $entity_label, $associated_on) {
        $keyword_norm = $this->aysa_keyword_mapping_execution_normalize_text($keyword);
        $label_norm = $this->aysa_keyword_mapping_execution_normalize_text($entity_label . ' ' . $target_url);
        if ($keyword_norm === '') {
            return false;
        }
        if ($this->aysa_keyword_mapping_execution_is_noise_keyword($keyword_norm)) {
            return false;
        }
        if ($this->aysa_keyword_mapping_execution_is_homepage_url($target_url)) {
            return $this->aysa_keyword_mapping_execution_is_homepage_safe_secondary($keyword_norm);
        }
        if ($this->aysa_keyword_mapping_execution_is_product_target($target_url, $associated_on, $entity_label)) {
            if ($this->aysa_keyword_mapping_execution_has_informational_intent($keyword_norm)) {
                return false;
            }
            if ($this->aysa_keyword_mapping_execution_has_quantity_mismatch($keyword_norm, $label_norm)) {
                return false;
            }
            if ($this->aysa_keyword_mapping_execution_is_broad_category_keyword($keyword_norm, $label_norm)) {
                return false;
            }
        }

        return true;
    }

    private function aysa_keyword_mapping_execution_secondary_limit($target_url, $associated_on) {
        if ($this->aysa_keyword_mapping_execution_is_homepage_url($target_url)) {
            return 20;
        }
        if ($this->aysa_keyword_mapping_execution_is_product_target($target_url, $associated_on, '')) {
            return 8;
        }
        return 20;
    }

    private function aysa_keyword_mapping_execution_normalize_text($value) {
        $value = remove_accents(strtolower(trim((string) $value)));
        $value = preg_replace('/[^a-z0-9\s]+/u', ' ', $value);
        $value = preg_replace('/\s+/', ' ', is_string($value) ? $value : '');
        return trim((string) $value);
    }

    private function aysa_keyword_mapping_execution_is_homepage_url($url) {
        $path = trim((string) wp_parse_url((string) $url, PHP_URL_PATH), '/');
        return $path === '';
    }

    private function aysa_keyword_mapping_execution_is_product_target($target_url, $associated_on, $entity_label) {
        $target_url = strtolower((string) $target_url);
        $entity_label = strtolower((string) $entity_label);
        return strpos($target_url, '/p/') !== false
            || strpos($target_url, '/product/') !== false
            || strpos($entity_label, 'pret') !== false
            || strpos($entity_label, 'buchet') !== false;
    }

    private function aysa_keyword_mapping_execution_is_noise_keyword($keyword_norm) {
        if (preg_match('/\b(near me|interflora|teleflora|amazon|emag|etsy|flora ri|florarir|florariw|floratie|floraria trandafirul)\b/u', $keyword_norm)) {
            return true;
        }
        if (strpos($keyword_norm, '_') !== false) {
            return true;
        }
        if (preg_match('/\b(uk|usa|nursery near me)\b/u', $keyword_norm)) {
            return true;
        }
        return false;
    }

    private function aysa_keyword_mapping_execution_is_homepage_safe_secondary($keyword_norm) {
        if (!$this->aysa_keyword_mapping_execution_has_commercial_intent($keyword_norm)) {
            return false;
        }
        if (!preg_match('/\b(bragadiru|ilfov|bucuresti|online|livrare|comanda|florarie|flori)\b/u', $keyword_norm)) {
            return false;
        }
        if (preg_match('/\b(buchet|buchete|bonsai|bujori|trandafiri|lalele|frezii|ghiveci|ursuleti|plus|sampanie|primavara|martie|indragostitilor|nastere)\b/u', $keyword_norm)) {
            return false;
        }
        return true;
    }

    private function aysa_keyword_mapping_execution_has_commercial_intent($keyword_norm) {
        return (bool) preg_match('/\b(florarie|flori|buchet|buchete|comanda|livrare|online|pret|cumpara|aranjamente|coroane)\b/u', $keyword_norm);
    }

    private function aysa_keyword_mapping_execution_has_informational_intent($keyword_norm) {
        return (bool) preg_match('/\b(cum|ce|cand|unde|idei|ghid|sfaturi|semnifica|inseamna|blog)\b/u', $keyword_norm);
    }

    private function aysa_keyword_mapping_execution_has_quantity_mismatch($keyword_norm, $label_norm) {
        preg_match_all('/\b\d+\b/u', $keyword_norm, $keyword_numbers);
        preg_match_all('/\b\d+\b/u', $label_norm, $label_numbers);
        $keyword_numbers = array_values(array_unique($keyword_numbers[0] ?? array()));
        $label_numbers = array_values(array_unique($label_numbers[0] ?? array()));
        if ($keyword_numbers === array() || $label_numbers === array()) {
            return false;
        }
        foreach ($keyword_numbers as $number) {
            if (!in_array($number, $label_numbers, true)) {
                return true;
            }
        }
        return false;
    }

    private function aysa_keyword_mapping_execution_is_broad_category_keyword($keyword_norm, $label_norm) {
        if (preg_match('/\b(buchete de flori|buchet trandafiri|flori trandafiri|flori online|florarie|flori bucuresti)\b/u', $keyword_norm)) {
            if (preg_match('/\b(3|5|7|9|11|13|15|18|19|21|25|27|30|31|51|101|rosii|mov|roz|mare)\b/u', $label_norm)) {
                return true;
            }
        }
        return false;
    }

    private function aysa_keyword_research_projection_resolve_target_by_url($url) {
        $url = esc_url_raw((string) $url);
        if ($url === '') {
            return null;
        }

        $post_id = url_to_postid($url);
        if ($post_id > 0) {
            $title = get_the_title($post_id);
            return array(
                'associated_on' => 'post',
                'id_intern' => $post_id,
                'value' => is_string($title) && $title !== '' ? $title : $url,
            );
        }

        $path = trim((string) wp_parse_url($url, PHP_URL_PATH), '/');
        if ($path !== '') {
            $slug = basename($path);
            $taxonomies = get_taxonomies(array('public' => true), 'names');
            foreach ((array) $taxonomies as $taxonomy) {
                $term = get_term_by('slug', $slug, $taxonomy);
                if (!$term || is_wp_error($term)) {
                    continue;
                }
                $term_link = get_term_link($term);
                if (is_string($term_link) && untrailingslashit($term_link) === untrailingslashit($url)) {
                    return array(
                        'associated_on' => 'term',
                        'id_intern' => intval($term->term_id),
                        'value' => (string) $term->name,
                    );
                }
            }
        }

        return array(
            'associated_on' => 'url',
            'id_intern' => intval(sprintf('%u', crc32(untrailingslashit($url)))),
            'value' => $url,
        );
    }

    private function aysa_keyword_mapping_ensure_local_associations($project_id, $allow_remote_fetch = true) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array(
                'ok' => false,
                'reason' => 'invalid_project_id',
            );
        }

        $existing = $this->get_keyword_discovery_local_associations($project_id);
        if (!empty($existing)) {
            $workspace = $this->aysa_keyword_v2_cache_get($project_id, 'keyword_mapping_v2');
            if (!is_array($workspace) || empty($workspace['clusters']) || !is_array($workspace['clusters'])) {
                return array(
                    'ok' => true,
                    'source' => 'local_option',
                    'count' => count($existing),
                );
            }

            $mapped_urls = array();
            foreach ((array) $workspace['clusters'] as $cluster) {
                if (!is_array($cluster)) {
                    continue;
                }
                $mapping_status = $this->aysa_keyword_mapping_onpage_status($cluster);
                if ($mapping_status === '') {
                    continue;
                }
                $target_url = $this->aysa_keyword_mapping_extract_cluster_target_url($cluster);
                $keywords = $this->aysa_keyword_mapping_cluster_keyword_candidates($cluster);
                $normalized_url = $this->aysa_keyword_mapping_normalize_url($target_url);
                if ($normalized_url !== '') {
                    $mapped_urls[$normalized_url] = true;
                    foreach ($keywords as $keyword) {
                        $keyword_value = trim((string) ($keyword['keyword'] ?? ''));
                        if ($keyword_value !== '') {
                            $mapped_urls[$normalized_url . '|' . strtolower($keyword_value)] = true;
                        }
                    }
                }
            }

            $existing_urls = array();
            foreach ((array) $existing as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $target_url = esc_url_raw((string) ($row['target_url'] ?? $row['link'] ?? ''));
                $normalized_url = $this->aysa_keyword_mapping_normalize_url($target_url);
                if ($normalized_url !== '') {
                    $existing_urls[$normalized_url] = true;
                    $keyword = trim((string) ($row['keyword'] ?? ''));
                    if ($keyword !== '') {
                        $existing_urls[$normalized_url . '|' . strtolower($keyword)] = true;
                    }
                }
            }

            if (!array_diff_key($mapped_urls, $existing_urls)) {
                return array(
                    'ok' => true,
                    'source' => 'local_option',
                    'count' => count($existing),
                );
            }
        }

        $workspace = $this->aysa_keyword_v2_cache_get($project_id, 'keyword_mapping_v2');
        if (!is_array($workspace) || empty($workspace['clusters']) || !is_array($workspace['clusters'])) {
            if (!$allow_remote_fetch) {
                return array(
                    'ok' => !empty($existing),
                    'source' => 'local_option',
                    'reason' => empty($existing) ? 'workspace_missing' : 'workspace_missing_using_existing',
                    'count' => count($existing),
                );
            }
            $payload = array(
                'project_id' => $project_id,
                'raw_context' => $this->aysa_collect_research_context_bundle($project_id),
                'force_refresh' => 0,
            );
            $result = $this->aysa_broker_request('POST', '/v1/research/workspaces/keyword-mapping', $payload, 240);
            if (is_wp_error($result)) {
                return array(
                    'ok' => false,
                    'reason' => 'workspace_fetch_failed',
                    'error' => $result->get_error_message(),
                );
            }
            if (is_array($result) && isset($result['data']) && is_array($result['data'])) {
                $result = $result['data'];
            }
            if (!is_array($result) || empty($result['ok'])) {
                return array(
                    'ok' => false,
                    'reason' => 'workspace_invalid',
                );
            }
            $workspace = $this->aysa_keyword_v2_finalize_snapshot_contract($project_id, 'keyword_mapping_v2', $result, 'clusters');
            $this->aysa_keyword_v2_cache_set($project_id, 'keyword_mapping_v2', $workspace);
        }

        return $this->aysa_keyword_mapping_sync_local_associations_from_workspace($project_id, $workspace);
    }

    private function aysa_keyword_mapping_sync_local_associations_from_workspace($project_id, $workspace) {
        $project_id = intval($project_id);
        $workspace = is_array($workspace) ? $workspace : array();
        $clusters = isset($workspace['clusters']) && is_array($workspace['clusters']) ? $workspace['clusters'] : array();
        if ($project_id <= 0 || empty($clusters)) {
            return array(
                'ok' => false,
                'reason' => 'workspace_missing',
            );
        }

        $association_rows = array();
        $keyword_rows = array();
        $seen = array();

        foreach ($clusters as $cluster) {
            if (!is_array($cluster)) {
                continue;
            }

            $selection_state = sanitize_key((string) ($cluster['selection_state'] ?? 'review'));
            if ($selection_state === 'excluded') {
                continue;
            }

            $mapping_status = $this->aysa_keyword_mapping_onpage_status($cluster);
            if ($mapping_status === '') {
                continue;
            }

            $target_url = $this->aysa_keyword_mapping_extract_cluster_target_url($cluster);
            if ($target_url === '') {
                continue;
            }

            $target = $this->aysa_keyword_mapping_resolve_local_association_target_by_url($target_url);
            if (!is_array($target) || empty($target['associated_on']) || intval($target['id_intern'] ?? 0) <= 0) {
                continue;
            }

            $keyword_candidates = $this->aysa_keyword_mapping_cluster_keyword_candidates($cluster, $selection_state);

            foreach ($keyword_candidates as $candidate) {
                $keyword_row = isset($candidate['row']) && is_array($candidate['row']) ? $candidate['row'] : array();
                $keyword = trim((string) ($keyword_row['keyword'] ?? ''));
                if ($keyword === '') {
                    continue;
                }
                $keyword_state = sanitize_key((string) ($keyword_row['selection_state'] ?? $selection_state));
                if ($keyword_state === 'excluded') {
                    continue;
                }

                $assoc_key = strtolower($keyword) . '|' . strtolower((string) $target['associated_on']) . '|' . intval($target['id_intern']);
                if (isset($seen[$assoc_key])) {
                    continue;
                }
                $seen[$assoc_key] = true;

                $association_rows[] = array(
                    'id' => $this->build_keyword_discovery_stable_local_id($keyword, (string) $target['associated_on'], intval($target['id_intern'])),
                    'projectId' => $project_id,
                    'keyword' => $keyword,
                    'associatedOn' => (string) $target['associated_on'],
                    'idIntern' => intval($target['id_intern']),
                    'value' => (string) ($target['value'] ?? $target_url),
                    'desiredPosition' => null,
                    'source' => 'keyword_mapping_v2',
                    'updatedAt' => current_time('mysql'),
                    'link' => $target_url,
                    'target_url' => $target_url,
                    'isMain' => intval($candidate['is_main'] ?? 0),
                );

                $keyword_rows[] = array(
                    'keyword' => $keyword,
                    'target_url' => $target_url,
                    'url' => $target_url,
                    'link' => $target_url,
                    'source' => 'association_seed',
                );
            }
        }

        if (empty($association_rows)) {
            return array(
                'ok' => false,
                'reason' => 'no_mapped_keyword_associations',
            );
        }

        $this->save_keyword_discovery_local_associations($project_id, $association_rows);
        global $wpdb;
        $table = esc_sql($wpdb->prefix . 'aysa_keyword_discovery');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if ($table_exists === $table) {
            $now = current_time('mysql');
            foreach ($keyword_rows as $row) {
                $keyword = trim((string) ($row['keyword'] ?? ''));
                $target_url = esc_url_raw((string) ($row['target_url'] ?? ''));
                if ($keyword === '' || $target_url === '') {
                    continue;
                }
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $existing_id = intval($wpdb->get_var($wpdb->prepare(
                    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                    "SELECT id FROM {$table} WHERE project_id = %d AND keyword = %s LIMIT 1",
                    $project_id,
                    $keyword
                )));
                $data = array(
                    'project_id' => $project_id,
                    'keyword' => $keyword,
                    'target_url' => $target_url,
                    'source' => 'association_seed',
                    'updated_at' => $now,
                );
                if ($existing_id > 0) {
                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                    $wpdb->update($table, $data, array('id' => $existing_id));
                } else {
                    $data['created_at'] = $now;
                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                    $wpdb->insert($table, $data);
                }
            }
        }

        return array(
            'ok' => true,
            'source' => 'keyword_mapping_v2',
            'count' => count($association_rows),
        );
    }

    private function aysa_keyword_mapping_extract_cluster_target_url($cluster) {
        $cluster = is_array($cluster) ? $cluster : array();
        $direct_url = esc_url_raw((string) ($cluster['target_url'] ?? ''));
        if ($direct_url !== '') {
            return $direct_url;
        }
        foreach (array('matched_page', 'selected_page') as $page_key) {
            $page = isset($cluster[$page_key]) && is_array($cluster[$page_key]) ? $cluster[$page_key] : array();
            foreach (array('url', 'permalink', 'target_url') as $url_key) {
                $url = esc_url_raw((string) ($page[$url_key] ?? ''));
                if ($url !== '') {
                    return $url;
                }
            }
        }

        $evidence = isset($cluster['mapping_evidence']) && is_array($cluster['mapping_evidence']) ? $cluster['mapping_evidence'] : array();
        $page = $evidence['page'] ?? '';
        if (is_array($page)) {
            foreach (array('url', 'permalink', 'target_url') as $url_key) {
                $url = esc_url_raw((string) ($page[$url_key] ?? ''));
                if ($url !== '') {
                    return $url;
                }
            }
        } else {
            $url = esc_url_raw((string) $page);
            if ($url !== '') {
                return $url;
            }
        }

        return esc_url_raw((string) ($cluster['gsc_selected_page_url'] ?? ''));
    }

    private function aysa_keyword_mapping_resolve_local_association_target_by_url($url) {
        $url = esc_url_raw((string) $url);
        if ($url === '') {
            return null;
        }

        $post_id = url_to_postid($url);
        if ($post_id > 0) {
            $post = get_post($post_id);
            if ($post instanceof WP_Post) {
                return array(
                    'associated_on' => $post->post_type === 'product' ? 'product' : ($post->post_type === 'post' ? 'post' : 'page'),
                    'id_intern' => $post_id,
                    'value' => get_the_title($post_id) ?: $url,
                );
            }
        }

        $path = trim((string) wp_parse_url($url, PHP_URL_PATH), '/');
        if ($path === '') {
            return null;
        }

        $post_types = get_post_types(array('public' => true), 'names');
        foreach (array($path, basename($path)) as $candidate_path) {
            $post = get_page_by_path($candidate_path, OBJECT, array_values((array) $post_types));
            if ($post instanceof WP_Post) {
                return array(
                    'associated_on' => $post->post_type === 'product' ? 'product' : ($post->post_type === 'post' ? 'post' : 'page'),
                    'id_intern' => intval($post->ID),
                    'value' => get_the_title($post->ID) ?: $url,
                );
            }
        }

        $slug = basename($path);
        $taxonomies = get_taxonomies(array('public' => true), 'names');
        foreach ((array) $taxonomies as $taxonomy) {
            $term = get_term_by('slug', $slug, $taxonomy);
            if (!$term || is_wp_error($term)) {
                continue;
            }
            $term_link = get_term_link($term);
            if (is_string($term_link) && $this->aysa_keyword_mapping_normalize_url($term_link) === $this->aysa_keyword_mapping_normalize_url($url)) {
                return array(
                    'associated_on' => 'category',
                    'id_intern' => intval($term->term_id),
                    'value' => (string) $term->name,
                );
            }
        }

        return null;
    }

    private function aysa_keyword_mapping_onpage_status($cluster) {
        $cluster = is_array($cluster) ? $cluster : array();
        $selection_state = sanitize_key((string) ($cluster['selection_state'] ?? 'review'));
        if ($selection_state === 'excluded') {
            return '';
        }
        $mapping_status = sanitize_key((string) ($cluster['mapping_status'] ?? ''));
        if (in_array($mapping_status, array('mapped', 'improve_existing_page'), true)) {
            return 'mapped';
        }
        if ($mapping_status === 'review') {
            return 'review';
        }
        return '';
    }

    private function aysa_keyword_mapping_cluster_keyword_candidates($cluster, $selection_state = 'review') {
        $cluster = is_array($cluster) ? $cluster : array();
        $selection_state = sanitize_key((string) $selection_state);
        $candidates = array();
        $seen = array();
        $add_keyword = static function ($value, $is_main) use (&$candidates, &$seen, $selection_state) {
            if (is_array($value)) {
                $value = $value['keyword'] ?? $value['query'] ?? $value['label'] ?? '';
            }
            $keyword = trim((string) $value);
            if ($keyword === '') {
                return;
            }
            $key = strtolower($keyword);
            if (isset($seen[$key])) {
                return;
            }
            $seen[$key] = true;
            $candidates[] = array(
                'row' => array(
                    'keyword' => $keyword,
                    'selection_state' => $selection_state,
                ),
                'keyword' => $keyword,
                'is_main' => $is_main ? 1 : 0,
            );
        };

        $add_keyword($cluster['primary_keyword'] ?? '', true);
        $add_keyword($cluster['primary_keyword_label'] ?? '', true);
        foreach (array('secondary_keywords', 'secondary_keyword_labels', 'member_keywords', 'keywords', 'supporting_keywords') as $key) {
            foreach ((array) ($cluster[$key] ?? array()) as $keyword) {
                $add_keyword($keyword, false);
            }
        }
        if (empty($candidates)) {
            $add_keyword($cluster['label'] ?? '', true);
        }
        return $candidates;
    }

    private function aysa_keyword_mapping_normalize_url($url) {
        $url = esc_url_raw((string) $url);
        if ($url === '') {
            return '';
        }
        $parts = wp_parse_url($url);
        if (!is_array($parts) || empty($parts['host'])) {
            return strtolower(untrailingslashit($url));
        }
        $path = isset($parts['path']) ? '/' . trim((string) $parts['path'], '/') : '';
        return strtolower((string) $parts['host'] . untrailingslashit($path));
    }

    private function aysa_keyword_research_projection_apply_content_strategy_rows($project_id, $run_id, $rows) {
        global $wpdb;

        $project_id = intval($project_id);
        $run_id = intval($run_id);
        if ($project_id <= 0 || $run_id <= 0 || !is_array($rows)) {
            return array('saved' => 0);
        }

        $table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if ($table_exists !== $table) {
            return array('saved' => 0, 'reason' => 'content_strategy_table_missing');
        }

        $plan_batch = 'keyword_research_run_' . $run_id;
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $wpdb->delete($table, array(
            'project_id' => $project_id,
            'plan_batch' => $plan_batch,
        ), array('%d', '%s'));

        $saved = 0;
        $inserted_ids = array();
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $title = sanitize_text_field((string) ($row['title'] ?? ''));
            if ($title === '') {
                continue;
            }

            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->insert($table, array(
                'project_id' => $project_id,
                'title' => $title,
                'keywords' => is_string($row['keywords'] ?? null) ? (string) $row['keywords'] : wp_json_encode($row['keywords'] ?? array()),
                'intent' => sanitize_text_field((string) ($row['intent'] ?? 'I')),
                'associated_page' => esc_url_raw((string) ($row['associated_page'] ?? '')),
                'strategy_type' => sanitize_key((string) ($row['strategy_type'] ?? $row['strategy_module'] ?? 'keyword_research')),
                'canonical_topic' => !empty($row['canonical_topic']) ? sanitize_text_field((string) $row['canonical_topic']) : null,
                'content_fingerprint' => !empty($row['content_fingerprint']) ? preg_replace('/[^a-f0-9]/', '', strtolower((string) $row['content_fingerprint'])) : null,
                'recommended_page_type' => sanitize_text_field((string) ($row['recommended_page_type'] ?? 'article')),
                'content_type' => sanitize_text_field((string) ($row['content_type'] ?? 'post')),
                'intent_layer' => !empty($row['intent_layer']) ? sanitize_key((string) $row['intent_layer']) : null,
                'journey_stage' => !empty($row['journey_stage']) ? sanitize_key((string) $row['journey_stage']) : null,
                'persona_key' => !empty($row['persona_key']) ? sanitize_key((string) $row['persona_key']) : null,
                'locality_key' => !empty($row['locality_key']) ? sanitize_key((string) $row['locality_key']) : null,
                'seasonality_key' => !empty($row['seasonality_key']) ? sanitize_key((string) $row['seasonality_key']) : null,
                'content_goal' => !empty($row['content_goal']) ? sanitize_key((string) $row['content_goal']) : null,
                'execution_stage' => sanitize_text_field((string) ($row['execution_stage'] ?? 'planned')),
                'status' => sanitize_text_field((string) ($row['status'] ?? 'proposed')),
                'duplicate_of_strategy_id' => !empty($row['duplicate_of_strategy_id']) ? intval($row['duplicate_of_strategy_id']) : null,
                'superseded_by_strategy_id' => !empty($row['superseded_by_strategy_id']) ? intval($row['superseded_by_strategy_id']) : null,
                'post_id' => null,
                'scheduled_for' => !empty($row['scheduled_for']) ? sanitize_text_field((string) $row['scheduled_for']) : null,
                'generated_at' => !empty($row['generated_at']) ? sanitize_text_field((string) $row['generated_at']) : null,
                'automation_status' => isset($row['automation_status']) ? sanitize_text_field((string) $row['automation_status']) : null,
                'automation_attempts' => intval($row['automation_attempts'] ?? 0),
                'plan_batch' => $plan_batch,
                'last_error' => isset($row['last_error']) ? sanitize_text_field((string) $row['last_error']) : null,
                'automation_updated_at' => !empty($row['automation_updated_at']) ? sanitize_text_field((string) $row['automation_updated_at']) : current_time('mysql'),
            ));
            if (!empty($wpdb->insert_id)) {
                $saved++;
                $inserted_ids[] = intval($wpdb->insert_id);
            }
        }

        if ($inserted_ids !== array()) {
            $this->aysa_keyword_research_reconcile_content_strategy_duplicates($project_id, $plan_batch, $inserted_ids);
        }

        return array('saved' => $saved);
    }

    private function aysa_keyword_research_reconcile_content_strategy_duplicates($project_id, $plan_batch, array $inserted_ids) {
        global $wpdb;

        $project_id = intval($project_id);
        if ($project_id <= 0 || $inserted_ids === array()) {
            return;
        }

        $table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        foreach ($inserted_ids as $inserted_id) {
            $inserted_id = intval($inserted_id);
            if ($inserted_id <= 0) {
                continue;
            }

            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $current = $wpdb->get_row($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT *
" .
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
                "                 FROM {$table}
                 WHERE id = %d
                   AND project_id = %d",
                $inserted_id,
                $project_id
            ), ARRAY_A);
            if (!is_array($current)) {
                continue;
            }

            if (!empty($current['duplicate_of_strategy_id']) || !empty($current['superseded_by_strategy_id'])) {
                continue;
            }

            $canonical_topic = sanitize_text_field((string) ($current['canonical_topic'] ?? ''));
            $content_fingerprint = sanitize_text_field((string) ($current['content_fingerprint'] ?? ''));
            $associated_page = untrailingslashit((string) ($current['associated_page'] ?? ''));
            $intent_layer = sanitize_key((string) ($current['intent_layer'] ?? ''));
            $journey_stage = sanitize_key((string) ($current['journey_stage'] ?? ''));
            $content_goal = sanitize_key((string) ($current['content_goal'] ?? ''));

            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $candidates = $wpdb->get_results($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT *
" .
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
                "                 FROM {$table}
                 WHERE project_id = %d
                   AND id <> %d
                   AND COALESCE(automation_status, '') <> 'deleted'
                   AND duplicate_of_strategy_id IS NULL
                   AND superseded_by_strategy_id IS NULL",
                $project_id,
                $inserted_id
            ), ARRAY_A);

            $exact = array();
            $near = array();
            foreach ((array) $candidates as $candidate) {
                if (!is_array($candidate)) {
                    continue;
                }
                if ($content_fingerprint !== '' && (string) ($candidate['content_fingerprint'] ?? '') === $content_fingerprint) {
                    $exact[] = $candidate;
                    continue;
                }
                if (
                    $canonical_topic !== ''
                    && (string) ($candidate['canonical_topic'] ?? '') === $canonical_topic
                    && sanitize_key((string) ($candidate['intent_layer'] ?? '')) === $intent_layer
                    && sanitize_key((string) ($candidate['journey_stage'] ?? '')) === $journey_stage
                    && sanitize_key((string) ($candidate['content_goal'] ?? '')) === $content_goal
                    && untrailingslashit((string) ($candidate['associated_page'] ?? '')) === $associated_page
                ) {
                    $near[] = $candidate;
                }
            }

            if ($exact !== array()) {
                $exact[] = $current;
                $canonical = $this->aysa_keyword_research_pick_canonical_strategy_row($exact, $plan_batch);
                $canonical_id = intval($canonical['id'] ?? 0);
                foreach ($exact as $row) {
                    $row_id = intval($row['id'] ?? 0);
                    if ($row_id <= 0 || $row_id === $canonical_id) {
                        continue;
                    }
                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                    $wpdb->update(
                        $table,
                        array(
                            'status' => 'duplicate',
                            'duplicate_of_strategy_id' => $canonical_id,
                            'automation_updated_at' => current_time('mysql'),
                        ),
                        array('id' => $row_id),
                        array('%s', '%d', '%s'),
                        array('%d')
                    );
                }
                if ($canonical_id !== $inserted_id) {
                    continue;
                }
            }

            if ($near !== array()) {
                $near[] = $current;
                $canonical = $this->aysa_keyword_research_pick_canonical_strategy_row($near, $plan_batch);
                $canonical_id = intval($canonical['id'] ?? 0);
                foreach ($near as $row) {
                    $row_id = intval($row['id'] ?? 0);
                    if ($row_id <= 0 || $row_id === $canonical_id) {
                        continue;
                    }
                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                    $wpdb->update(
                        $table,
                        array(
                            'status' => 'superseded',
                            'superseded_by_strategy_id' => $canonical_id,
                            'automation_updated_at' => current_time('mysql'),
                        ),
                        array('id' => $row_id),
                        array('%s', '%d', '%s'),
                        array('%d')
                    );
                }
            }
        }
    }

    private function aysa_keyword_research_pick_canonical_strategy_row(array $rows, $current_plan_batch) {
        $best = null;
        $best_score = null;
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $score = $this->aysa_keyword_research_content_strategy_rank($row, $current_plan_batch);
            if ($best === null || $score > $best_score) {
                $best = $row;
                $best_score = $score;
            }
        }
        return is_array($best) ? $best : array();
    }

    private function aysa_keyword_research_content_strategy_rank(array $row, $current_plan_batch) {
        $score = 0;
        $status = strtolower((string) ($row['status'] ?? ''));
        $automation_status = strtolower((string) ($row['automation_status'] ?? ''));
        $plan_batch = (string) ($row['plan_batch'] ?? '');
        $post_id = intval($row['post_id'] ?? 0);
        $scheduled_for = trim((string) ($row['scheduled_for'] ?? ''));

        if ($post_id > 0 || $status === 'generated' || $automation_status === 'generated') {
            $score += 1000;
        } elseif ($status === 'scheduled' || $scheduled_for !== '' || in_array($automation_status, array('processing', 'queued', 'retry'), true)) {
            $score += 500;
        } elseif ($plan_batch === (string) $current_plan_batch) {
            $score += 250;
        } else {
            $score += 100;
        }

        $score += intval($row['id'] ?? 0);
        return $score;
    }

    public function handle_monitoring_conversation() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }
        if (!$this->check_authentication(false)) {
            wp_send_json_error(array('message' => 'Authentication required.'));
            return;
        }

        $key_validation = $this->aysa_validate_test_access_key(
            $this->aysa_get_effective_access_key(isset($_POST['access_key']) ? sanitize_text_field((string) wp_unslash($_POST['access_key'])) : ''),
            isset($_POST['site_host']) ? sanitize_text_field((string) wp_unslash($_POST['site_host'])) : ''
        );
        if (empty($key_validation['valid'])) {
            wp_send_json_error(array(
                'message' => (string) ($key_validation['message'] ?? 'Access key is invalid.'),
                'code' => 'access_key_invalid',
            ));
            return;
        }

        $mode = isset($_POST['mode']) ? sanitize_key((string) wp_unslash($_POST['mode'])) : 'bootstrap';
        $conversation_language = isset($_POST['conversation_language']) ? sanitize_key((string) wp_unslash($_POST['conversation_language'])) : 'en';
        if (!in_array($conversation_language, array('en', 'ro', 'bg'), true)) {
            $conversation_language = 'en';
        }
        $project_id = $this->aysa_resolve_installed_project_id(isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0);
        $user_message = isset($_POST['user_message']) ? sanitize_textarea_field((string) wp_unslash($_POST['user_message'])) : '';
        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Missing project ID.'));
            return;
        }

        $auth_manager = Aysa_Auth_Manager::get_instance();
        $auth_status = $auth_manager->is_authenticated();
        $user_id = isset($auth_status['aysa_user_id']) ? intval($auth_status['aysa_user_id']) : 0;
        if ($user_id <= 0) {
            wp_send_json_error(array('message' => 'Invalid authentication context.'));
            return;
        }

        $client_state = array();
        if (isset($_POST['client_state'])) {
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
            $decoded_client_state = json_decode((string) wp_unslash($_POST['client_state']), true);
            if (is_array($decoded_client_state)) {
                $client_state = $decoded_client_state;
            }
        }

        $local_setup_state = $this->aysa_get_local_setup_state_for_user($user_id, $project_id);
        $general = isset($local_setup_state['general']) && is_array($local_setup_state['general']) ? $local_setup_state['general'] : array();
        $website = isset($general['website']) ? $this->aysa_setup_normalize_url((string) $general['website']) : '';
        if ($website === '') {
            $website = $this->aysa_setup_normalize_url(home_url('/'));
        }

        $monitoringState = array(
            'tracked_count' => intval($client_state['tracked_count'] ?? 0),
            'refreshed_count' => intval($client_state['refreshed_count'] ?? 0),
            'stale_count' => intval($client_state['stale_count'] ?? 0),
            'stale_preview' => isset($client_state['stale_preview']) && is_array($client_state['stale_preview']) ? $client_state['stale_preview'] : array(),
            'job_status' => sanitize_text_field((string) ($client_state['job_status'] ?? 'idle')),
            'job_processed' => intval($client_state['job_processed'] ?? 0),
            'job_total' => intval($client_state['job_total'] ?? 0),
            'wallet_balance' => isset($client_state['wallet_balance']) ? intval($client_state['wallet_balance']) : null,
            'wallet_spendable' => isset($client_state['wallet_spendable']) ? intval($client_state['wallet_spendable']) : null,
            'wallet_reserved_links' => isset($client_state['wallet_reserved_links']) ? intval($client_state['wallet_reserved_links']) : null,
        );

        $endpoint = '/v1/conversation/monitoring/bootstrap';
        $payload = array(
            'project_id' => $project_id,
            'conversation_language' => $conversation_language,
            'reset_session' => !empty($_POST['reset_session']),
            'site_host' => isset($_POST['site_host']) ? sanitize_text_field((string) wp_unslash($_POST['site_host'])) : '',
            'website' => $website,
            'local_setup_state' => $local_setup_state,
            'ui_context' => array(
                'conversation_language' => $conversation_language,
                'available_languages' => array('en', 'ro', 'bg'),
                'page' => 'monitoring_analytics',
            ),
            'state' => array(
                'monitoring' => $monitoringState,
            ),
        );

        if ($mode === 'message') {
            $endpoint = '/v1/conversation/monitoring/message';
            $payload['user_message'] = $user_message;
        }

        $result = $this->aysa_broker_request('POST', $endpoint, $payload, 45);
        if (is_wp_error($result)) {
            wp_send_json_error(array('message' => $result->get_error_message()));
            return;
        }
        if (!is_array($result) || empty($result['ok'])) {
            wp_send_json_error(array(
                'message' => isset($result['error']) ? (string) $result['error'] : 'Monitoring conversation request failed.',
                'result' => $result,
            ));
            return;
        }

        wp_send_json_success(array(
            'result' => $result,
            'access' => $key_validation,
        ));
    }

    private function aysa_monitoring_build_conversation_bootstrap($project_id, $language, array $client_state) {
        $summary = $this->aysa_monitoring_conversation_summary($project_id, $client_state);
        $message = match ($language) {
            'ro' => 'Sunt conectat la Monitoring & Analytics. Urmăresc ' . $summary['tracked'] . ' keyworduri, dintre care ' . $summary['refreshed'] . ' au date salvate și ' . $summary['stale'] . ' așteaptă o verificare aprobată. Pot să-ți arăt datele salvate, statusul sau preflight-ul de refresh; nu pornesc refresh fără aprobare și cost maxim.',
            'bg' => 'Свързан съм с Monitoring & Analytics. Следя ' . $summary['tracked'] . ' ключови думи, от които ' . $summary['refreshed'] . ' имат запазени данни, а ' . $summary['stale'] . ' чакат одобрена проверка. Не стартирам refresh без одобрение и лимит.',
            default => 'I am connected to Monitoring & Analytics. I see ' . $summary['tracked'] . ' tracked keywords, with ' . $summary['refreshed'] . ' stored locally and ' . $summary['stale'] . ' waiting for an approved check. I do not start refresh without approval and a cost cap.',
        };

        return array(
            'assistant_message' => $message,
            'messages' => array(
                array('role' => 'assistant', 'message' => $message),
            ),
            'suggested_actions' => $this->aysa_monitoring_conversation_suggested_actions($language, $summary),
            'state' => array(
                'meta' => array(
                    'project_id' => $project_id,
                    'conversation_area' => 'monitoring_analytics',
                ),
            ),
        );
    }

    private function aysa_monitoring_build_conversation_reply($project_id, $language, $user_message, array $client_state) {
        $summary = $this->aysa_monitoring_conversation_summary($project_id, $client_state);
        $normalized = $this->aysa_monitoring_conversation_normalize($user_message);
        $execution_action = '';
        $ui_action = '';
        $message = '';

        if ($normalized === '' || $this->aysa_monitoring_conversation_has_any($normalized, array('overview', 'summary', 'rezumat', 'sumar', 'status general', 'general status'))) {
            $message = $this->aysa_monitoring_conversation_overview_message($language, $summary);
        } elseif ($this->aysa_monitoring_conversation_has_any($normalized, array('ce pozitii am', 'ce poziții am', 'pozitii', 'poziții', 'ranking', 'rankings', 'cum stau in google', 'cum stau în google'))) {
            $ui_action = 'open_monitoring_tab';
            $message = match ($language) {
                'ro' => 'Îți arăt pozițiile din verificările SERP salvate. Dacă datele lipsesc sau sunt vechi, următorul pas este preflight-ul de refresh cu aprobare și cost maxim.',
                'bg' => 'Показвам позициите от запазените SERP проверки. Ако липсват или са стари, следва preflight с одобрение и лимит.',
                default => 'I show positions from stored SERP checks. If data is missing or stale, the next safe step is refresh preflight with approval and a cost cap.',
            };
        } elseif ($this->aysa_monitoring_conversation_has_any($normalized, array('ce a scazut', 'ce a scăzut', 'pierd trafic', 'scaderi', 'scăderi', 'dropped', 'regresii'))) {
            $ui_action = 'open_monitoring_tab';
            $message = match ($language) {
                'ro' => 'Trendurile și regresiile se calculează doar din două verificări comparabile. Dacă există doar baseline, nu inventez scăderi.',
                'bg' => 'Трендовете се изчисляват само от две сравними проверки. Ако има само baseline, не измислям спадове.',
                default => 'Trends and regressions use only two comparable stored checks. With baseline only, I do not invent drops.',
            };
        } elseif ($this->aysa_monitoring_conversation_has_any($normalized, array('raport', 'rapoarte', 'raportul saptamanal', 'raportul săptămânal', 'weekly report', 'monthly report'))) {
            $ui_action = 'open_monitoring_tab';
            $message = match ($language) {
                'ro' => 'Rapoartele folosesc doar date salvate. Pot arăta ce rapoarte sunt posibile și ce lipsește, dar nu generez sau trimit raport în acest pas.',
                'bg' => 'Докладите използват само запазени данни. Показвам какво е възможно и какво липсва, без да генерирам или изпращам доклад.',
                default => 'Reports use stored data only. I can show possible reports and missing data, but I do not generate or send a report in this step.',
            };
        } elseif ($this->aysa_monitoring_conversation_has_any($normalized, array('refresh', 'reimprosp', 'reîmprosp', 'actualizeaza', 'actualizează', 'update data', 'start refresh'))) {
            $ui_action = 'open_monitoring_tab';
            $message = match ($language) {
                'ro' => 'Nu pornesc refresh din conversație. Monitoring are nevoie de aprobare explicită și limită maximă de cost înainte de provider, job sau debit.',
                'bg' => 'Не стартирам refresh от разговор. Monitoring изисква изрично одобрение и максимален лимит на цена преди provider, job или дебит.',
                default => 'I will not start refresh from conversation. Monitoring needs explicit approval and a maximum cost cap before provider work, jobs, or debit.',
            };
        } elseif ($this->aysa_monitoring_conversation_has_any($normalized, array('job', 'progress', 'status', 'running', 'merge refresh', 'refresh status', 'progres', 'merge jobul'))) {
            $ui_action = 'open_monitoring_tab';
            $message = match ($language) {
                'ro' => 'Îți arăt statusul salvat al verificărilor în tabul Scheduler. Nu pornesc polling, worker, provider sau refresh din conversație.',
                'bg' => 'Показвам запазения статус в Scheduler. Не стартирам polling, worker, provider или refresh от разговора.',
                default => 'I show the stored check status in Scheduler. I do not start polling, workers, providers, or refresh from conversation.',
            };
        } elseif ($this->aysa_monitoring_conversation_has_any($normalized, array('stale', 'not refreshed', 'unrefreshed', 'neresh', 'neactualizat', 'neactualizate', 'neactualizatele', 'nerefreshate'))) {
            $ui_action = 'open_monitoring_rows';
            $message = match ($language) {
                'ro' => 'Deschid tabelul cu keyworduri. În momentul ăsta ai ' . $summary['stale'] . ' keyworduri care nu sunt încă refreshate. Primele sunt: ' . $summary['stale_preview'] . '.',
                'bg' => 'Отварям таблицата с ключови думи. В момента имаш ' . $summary['stale'] . ' ключови думи, които още не са обновени. Първите са: ' . $summary['stale_preview'] . '.',
                default => 'I am opening the keyword table. Right now you have ' . $summary['stale'] . ' keywords that are not refreshed yet. The first ones are: ' . $summary['stale_preview'] . '.',
            };
        } elseif ($this->aysa_monitoring_conversation_has_any($normalized, array('credits', 'wallet', 'credite', 'sold', 'balance'))) {
            $ui_action = 'open_monitoring_tab';
            $message = match ($language) {
                'ro' => 'Îți pot arăta politica de cost din preflight-ul de refresh. Nu refac soldul și nu consum credite din conversație.',
                'bg' => 'Мога да покажа политиката за разход в preflight. Не обновявам баланса и не харча кредити от разговора.',
                default => 'I can show the refresh preflight cost policy. I do not refresh wallet balance or spend credits from conversation.',
            };
        } elseif ($this->aysa_monitoring_conversation_has_any($normalized, array('rows', 'keywords', 'keyworduri', 'cuvinte cheie', 'tracked keywords', 'tracked keyword'))) {
            $ui_action = 'open_monitoring_rows';
            $message = match ($language) {
                'ro' => 'Deschid tabelul de Monitoring. Ai ' . $summary['tracked'] . ' keyworduri urmărite și ' . $summary['refreshed'] . ' dintre ele au date locale deja persistate.',
                'bg' => 'Отварям таблицата Monitoring. Имаш ' . $summary['tracked'] . ' следени ключови думи и ' . $summary['refreshed'] . ' от тях вече имат локално запазени данни.',
                default => 'I am opening the Monitoring table. You have ' . $summary['tracked'] . ' tracked keywords and ' . $summary['refreshed'] . ' of them already have local persisted data.',
            };
        } else {
            $message = match ($language) {
                'ro' => 'Te pot ajuta aici cu monitoringul keywordurilor: verific statusul, arăt keywordurile nereshate sau explic ce lipsește. Refresh-ul cere aprobare separată cu limită de cost.',
                'bg' => 'Тук мога да помогна с monitoring-а на ключовите думи: проверявам статус, показвам неосвежените ключови думи или обяснявам какво липсва. Refresh изисква отделно одобрение с лимит на цена.',
                default => 'Here I can help with keyword monitoring: check status, show stale keywords, or explain what is missing. Refresh requires separate cost-cap approval.',
            };
        }

        return array(
            'assistant_message' => $message,
            'messages' => array(
                array('role' => 'user', 'message' => $user_message),
                array('role' => 'assistant', 'message' => $message),
            ),
            'suggested_actions' => $this->aysa_monitoring_conversation_suggested_actions($language, $summary),
            'ui_action' => $ui_action,
            'execution_action' => $execution_action,
            'state' => array(
                'meta' => array(
                    'project_id' => $project_id,
                    'conversation_area' => 'monitoring_analytics',
                ),
            ),
        );
    }

    private function aysa_monitoring_conversation_summary($project_id, array $client_state) {
        $tracked = intval($client_state['tracked_count'] ?? 0);
        $refreshed = intval($client_state['refreshed_count'] ?? 0);
        $stale = intval($client_state['stale_count'] ?? max(0, $tracked - $refreshed));
        $job_status = sanitize_text_field((string) ($client_state['job_status'] ?? 'idle'));
        $job_processed = intval($client_state['job_processed'] ?? 0);
        $job_total = intval($client_state['job_total'] ?? 0);
        $rows_preview = isset($client_state['stale_preview']) && is_array($client_state['stale_preview']) ? $client_state['stale_preview'] : array();
        $preview = array();
        foreach ($rows_preview as $row) {
            $keyword = sanitize_text_field((string) ($row['keyword'] ?? ''));
            if ($keyword !== '') {
                $preview[] = $keyword;
            }
            if (count($preview) >= 3) {
                break;
            }
        }

        return array(
            'project_id' => intval($project_id),
            'tracked' => $tracked,
            'refreshed' => $refreshed,
            'stale' => $stale,
            'job_status' => $job_status,
            'job_processed' => $job_processed,
            'job_total' => $job_total,
            'stale_preview' => !empty($preview) ? implode(', ', $preview) : 'n/a',
        );
    }

    private function aysa_monitoring_conversation_overview_message($language, array $summary) {
        return match ($language) {
            'ro' => 'În monitoring ai ' . $summary['tracked'] . ' keyworduri urmărite. ' . $summary['refreshed'] . ' au date locale persistate, iar ' . $summary['stale'] . ' sunt încă stale. Jobul de refresh este acum `' . $summary['job_status'] . '` și a procesat ' . $summary['job_processed'] . ' din ' . $summary['job_total'] . '.',
            'bg' => 'В monitoring имаш ' . $summary['tracked'] . ' следени ключови думи. ' . $summary['refreshed'] . ' имат локално запазени данни, а ' . $summary['stale'] . ' още са stale. Refresh job-ът е `' . $summary['job_status'] . '` и е обработил ' . $summary['job_processed'] . ' от ' . $summary['job_total'] . '.',
            default => 'In Monitoring you have ' . $summary['tracked'] . ' tracked keywords. ' . $summary['refreshed'] . ' already have local persisted data, and ' . $summary['stale'] . ' are still stale. The refresh job is currently `' . $summary['job_status'] . '` and processed ' . $summary['job_processed'] . ' out of ' . $summary['job_total'] . '.',
        };
    }

    private function aysa_monitoring_conversation_suggested_actions($language, array $summary) {
        return array(
            array(
                'label' => match ($language) {
                    'ro' => 'Pregătește aprobare refresh',
                    'bg' => 'Подготви одобрение за refresh',
                    default => 'Prepare refresh approval',
                },
                'ui_action' => 'refresh_requires_approval',
            ),
            array(
                'label' => match ($language) {
                    'ro' => 'Vezi keywordurile',
                    'bg' => 'Виж ключовите думи',
                    default => 'Show keywords',
                },
                'ui_action' => 'open_monitoring_rows',
            ),
            array(
                'label' => match ($language) {
                    'ro' => 'Vezi Scheduler',
                    'bg' => 'Виж Scheduler',
                    default => 'Show Scheduler',
                },
                'ui_action' => 'open_monitoring_tab',
                'modal_tab' => 'scheduler',
            ),
        );
    }

    private function aysa_monitoring_conversation_normalize($value) {
        $value = strtolower(trim((string) $value));
        $value = str_replace(array('ă', 'â', 'î', 'ș', 'ş', 'ț', 'ţ'), array('a', 'a', 'i', 's', 's', 't', 't'), $value);
        return preg_replace('/\s+/', ' ', $value);
    }

    private function aysa_monitoring_conversation_has_any($haystack, array $patterns) {
        foreach ($patterns as $pattern) {
            if ($pattern !== '' && strpos($haystack, (string) $pattern) !== false) {
                return true;
            }
        }
        return false;
    }

    public function handle_adjustments_generation_notifications() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }
        if (!$this->check_authentication(false)) {
            wp_send_json_error(array('message' => 'Authentication required.'));
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0);
        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Missing project ID.'));
            return;
        }
        $conversation_language = isset($_POST['conversation_language']) ? sanitize_key((string) wp_unslash($_POST['conversation_language'])) : 'en';
        if (!in_array($conversation_language, array('en', 'ro', 'bg'), true)) {
            $conversation_language = 'en';
        }

        $this->aysa_adjustments_process_generation_queue_now($project_id, 3);

        wp_send_json_success(array(
            'notifications' => $this->aysa_adjustments_collect_generation_notifications($project_id, $conversation_language),
            'approvals' => $this->aysa_adjustments_collect_pending_approval_notifications($project_id, $conversation_language),
        ));
    }

    public function handle_setup_notifications() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }
        if (!$this->check_authentication(false)) {
            wp_send_json_error(array('message' => 'Authentication required.'));
            return;
        }

        $key_validation = $this->aysa_validate_test_access_key(
            $this->aysa_get_effective_access_key(isset($_POST['access_key']) ? sanitize_text_field(wp_unslash($_POST['access_key'])) : ''),
            isset($_POST['site_host']) ? sanitize_text_field((string) wp_unslash($_POST['site_host'])) : ''
        );
        if (empty($key_validation['valid'])) {
            wp_send_json_error(array(
                'message' => (string) ($key_validation['message'] ?? 'Access key is invalid.'),
                'code' => 'access_key_invalid',
            ));
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0);
        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Missing project ID.'));
            return;
        }

        $conversation_language = isset($_POST['conversation_language']) ? sanitize_key((string) wp_unslash($_POST['conversation_language'])) : 'en';
        if (!in_array($conversation_language, array('en', 'ro', 'bg'), true)) {
            $conversation_language = 'en';
        }

        $run_token = $this->aysa_get_or_create_onboarding_run_token($project_id);
        $payload = array(
            'project_id' => $project_id,
            'channel' => 'setup_seo_profile',
            'conversation_language' => $conversation_language,
            'mark_delivered' => !empty($_POST['mark_delivered']),
            'run_token' => $run_token,
            'discard_mismatched' => true,
        );
        if (!empty($_POST['notification_ids']) && is_array($_POST['notification_ids'])) {
            $payload['notification_ids'] = array_values(array_filter(array_map('intval', wp_unslash($_POST['notification_ids'])), static function ($id) {
                return $id > 0;
            }));
        }
        $result = $this->aysa_broker_request('POST', '/v1/onboarding/notifications/pull', $payload, 30);
        if (is_wp_error($result)) {
            wp_send_json_error(array('message' => $result->get_error_message()));
            return;
        }
        if (!is_array($result) || empty($result['ok'])) {
            wp_send_json_error(array(
                'message' => isset($result['error']) ? (string) $result['error'] : 'Failed to load onboarding notifications.',
            ));
            return;
        }

        if (!isset($result['notifications']) || !is_array($result['notifications'])) {
            $result['notifications'] = array();
        }
        $result['run_token'] = $run_token;
        $result['notification_count'] = count($result['notifications']);
        wp_send_json_success($result);
    }

    public function handle_workspace_state_save() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!$this->aysa_current_user_can_workspace()) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }

        $tab = isset($_POST['tab']) ? sanitize_text_field((string) wp_unslash($_POST['tab'])) : '';
        $subtab = isset($_POST['subtab']) ? sanitize_text_field((string) wp_unslash($_POST['subtab'])) : '';
        $normalized_tab = $this->aysa_normalize_research_tab($tab);
        $saved = array();

        if ($normalized_tab !== '') {
            $this->aysa_save_last_research_tab($normalized_tab);
            $saved['tab'] = $normalized_tab;
        }

        if ($normalized_tab === 'keyword-discovery' && $subtab !== '') {
            $saved_subtab = $this->aysa_save_last_keyword_discovery_subtab($subtab);
            if ($saved_subtab !== '') {
                $saved['subtab'] = $saved_subtab;
            }
        }

        wp_send_json_success(array(
            'saved' => $saved,
        ));
    }

    public function handle_adjustments_generate_created_entity($payload) {
        $payload = is_array($payload) ? $payload : array();
        $queue_key = sanitize_text_field((string) ($payload['queue_key'] ?? ''));
        $project_id = $this->aysa_resolve_installed_project_id(intval($payload['project_id'] ?? 0));
        if ($queue_key === '' || $project_id <= 0) {
            return;
        }

        $queue = $this->aysa_adjustments_get_generation_queue($project_id);
        if (!isset($queue[$queue_key]) || !is_array($queue[$queue_key])) {
            return;
        }

        $item = $queue[$queue_key];
        $item['status'] = 'processing';
        $item['updated_at'] = current_time('mysql');
        $queue[$queue_key] = $item;
        $this->aysa_adjustments_save_generation_queue($project_id, $queue);

        if (!class_exists('Aysa_Seo_Generation_Processor')) {
            $processor_file = plugin_dir_path(dirname(__FILE__)) . 'includes/automation/processors/class-seo-generation-processor.php';
            if (file_exists($processor_file)) {
                require_once $processor_file;
            }
        }

        if (!class_exists('Aysa_Seo_Generation_Processor')) {
            $item['status'] = 'failed';
            $item['error'] = 'SEO generation processor is not available.';
            $item['updated_at'] = current_time('mysql');
            $queue[$queue_key] = $item;
            $this->aysa_adjustments_save_generation_queue($project_id, $queue);
            return;
        }

        $processor = new Aysa_Seo_Generation_Processor();
        $result = $processor->process(array(
            'entity_id' => intval($item['entity_id'] ?? 0),
            'entity_type' => (string) ($item['entity_processor_type'] ?? ''),
            'keyword' => (string) ($item['keyword'] ?? ''),
            'project_id' => $project_id,
        ));

        $item['updated_at'] = current_time('mysql');
        if (is_wp_error($result)) {
            $item['status'] = 'failed';
            $item['error'] = $result->get_error_message();
        } else {
            $item['status'] = 'generated';
            $item['review_id'] = intval($result['review_id'] ?? 0);
            $item['result_message'] = sanitize_text_field((string) ($result['message'] ?? ''));
        }

        $queue[$queue_key] = $item;
        $this->aysa_adjustments_save_generation_queue($project_id, $queue);
    }

    private function aysa_adjustments_get_keyword_research_snapshot($project_id, $setup_user_id = 0) {
        $project_id = intval($project_id);
        $setup_user_id = intval($setup_user_id);

        $profile_user_id = ($setup_user_id > 0 ? $setup_user_id : get_current_user_id());
        $seed_data = $this->aysa_keyword_strategy_extract_seed_data($profile_user_id);
        $shared_user_id = intval(get_option('aysa_user_id', 0));
        if (
            (empty($seed_data['seed_keywords']) || empty($seed_data['competitors']))
            && $shared_user_id > 0
            && $shared_user_id !== $profile_user_id
        ) {
            $shared_seed_data = $this->aysa_keyword_strategy_extract_seed_data($shared_user_id);
            if (empty($seed_data['seed_keywords']) && !empty($shared_seed_data['seed_keywords']) && is_array($shared_seed_data['seed_keywords'])) {
                $seed_data['seed_keywords'] = array_values($shared_seed_data['seed_keywords']);
            }
            if (empty($seed_data['competitors']) && !empty($shared_seed_data['competitors']) && is_array($shared_seed_data['competitors'])) {
                $seed_data['competitors'] = array_values($shared_seed_data['competitors']);
            }
        }
        if ((empty($seed_data['seed_keywords']) || empty($seed_data['competitors'])) && $project_id > 0) {
            $project_competition = get_option('aysa_wizard_competition_project_' . $project_id, array());
            if (is_array($project_competition) && !empty($project_competition)) {
                $project_seed_keywords = array();
                $project_competitors = array();
                foreach ($project_competition as $key => $value) {
                    $value = trim((string) $value);
                    if ($value === '') {
                        continue;
                    }
                    if (preg_match('/^keyword\\d+$/', (string) $key)) {
                        $project_seed_keywords[] = $value;
                        continue;
                    }
                    if (preg_match('/^competitor\\d+$/', (string) $key)) {
                        $project_competitors[] = $value;
                    }
                }
                if (empty($seed_data['seed_keywords']) && !empty($project_seed_keywords)) {
                    $seed_data['seed_keywords'] = array_values(array_unique($project_seed_keywords));
                }
                if (empty($seed_data['competitors']) && !empty($project_competitors)) {
                    $seed_data['competitors'] = array_values(array_unique($project_competitors));
                }
            }
        }
        $seed_keywords = isset($seed_data['seed_keywords']) && is_array($seed_data['seed_keywords']) ? array_values($seed_data['seed_keywords']) : array();
        $status = $this->aysa_keyword_discovery_get_status($project_id);
        $snapshot = get_option($this->aysa_keyword_discovery_snapshot_key($project_id), array());

        $association_rows = $this->get_keyword_discovery_local_associations($project_id);
        $gsc_rows = $this->aysa_adjustments_get_local_keywords_by_sources($project_id, array('gsc', 'gsc_query', 'gsc_query_only'));
        $suggestion_rows = $this->aysa_keyword_discovery_get_local_keywords_payload($project_id, 'keyword_suggestions');
        $selected_gap_rows = $this->aysa_adjustments_get_local_keywords_by_sources($project_id, array('competitor_gap'));
        $unified_rows = $this->aysa_adjustments_build_unified_keyword_rows(
            $project_id,
            $gsc_rows,
            $suggestion_rows,
            isset($selected_gap_rows) && is_array($selected_gap_rows) ? $selected_gap_rows : array(),
            $association_rows
        );
        $gsc_count = count($gsc_rows);
        $suggestions_count = count($suggestion_rows);

        return array(
            'project_id' => $project_id,
            'seed_keywords' => $seed_keywords,
            'gsc_count' => $gsc_count,
            'suggestions_count' => $suggestions_count,
            'competitor_gap_selected_count' => count($selected_gap_rows),
            'unified_keywords_count' => count($unified_rows),
            'associations_count' => is_array($association_rows) ? count($association_rows) : 0,
            'gsc_rows' => is_array($gsc_rows) ? $gsc_rows : array(),
            'suggestion_rows' => is_array($suggestion_rows) ? $suggestion_rows : array(),
            'competitor_gap_rows' => is_array($selected_gap_rows) ? $selected_gap_rows : array(),
            'unified_rows' => $unified_rows,
            'association_rows' => is_array($association_rows) ? $association_rows : array(),
            'tracked_keywords_plan' => $this->aysa_get_keyword_tracking_plan($project_id),
            'refresh_status' => $status,
            'background_snapshot' => is_array($snapshot) ? array(
                'count' => intval($snapshot['count'] ?? 0),
                'fetched_at' => intval($snapshot['fetched_at'] ?? 0),
                'run_id' => (string) ($snapshot['run_id'] ?? ''),
            ) : array(),
            'has_existing_data' => ($gsc_count > 0 || $suggestions_count > 0),
        );
    }

    private function aysa_adjustments_get_recent_relevant_keywords_option_key($project_id) {
        return 'aysa_adjustments_recent_relevant_keywords_' . intval($project_id);
    }

    private function aysa_keyword_tracking_plan_option_key($project_id) {
        return 'aysa_keyword_tracking_plan_' . intval($project_id);
    }

    private function aysa_adjustments_pending_approvals_option_key($project_id) {
        return 'aysa_adjustments_pending_approvals_' . intval($project_id);
    }

    private function aysa_adjustments_get_pending_approvals($project_id) {
        $value = get_option($this->aysa_adjustments_pending_approvals_option_key($project_id), array());
        return is_array($value) ? array_values($value) : array();
    }

    private function aysa_adjustments_save_pending_approvals($project_id, $items) {
        update_option(
            $this->aysa_adjustments_pending_approvals_option_key($project_id),
            is_array($items) ? array_values($items) : array(),
            false
        );
    }

    private function aysa_adjustments_approval_action_url($ui_action) {
        $ui_action = sanitize_key((string) $ui_action);
        return match ($ui_action) {
            'open_content_strategy', 'open_artifact_content_strategy' => admin_url('admin.php?page=aysa-research&tab=content-strategy'),
            'open_competitor_gap' => admin_url('admin.php?page=aysa-research&tab=competitor-gap'),
            'open_competitor_backlinks' => admin_url('admin.php?page=aysa-research&tab=competitor-backlinks'),
            'open_missing_pages' => admin_url('admin.php?page=aysa-research&tab=missing-pages'),
            default => admin_url('admin.php?page=aysa-research&tab=keyword-discovery'),
        };
    }

    private function aysa_adjustments_pending_approval_email_recipients() {
        $recipients = array();

        $admin_email = sanitize_email((string) get_option('admin_email', ''));
        if ($admin_email !== '' && is_email($admin_email)) {
            $recipients[] = $admin_email;
        }

        $current_user = function_exists('wp_get_current_user') ? wp_get_current_user() : null;
        if ($current_user && !empty($current_user->user_email)) {
            $user_email = sanitize_email((string) $current_user->user_email);
            if ($user_email !== '' && is_email($user_email)) {
                $recipients[] = $user_email;
            }
        }

        return array_values(array_unique($recipients));
    }

    private function get_app_aysa_internal_secret() {
        if (defined('AYSA_WALLET_APP_INTERNAL_SECRET')) {
            return trim((string) AYSA_WALLET_APP_INTERNAL_SECRET);
        }

        $stored = trim((string) get_option('aysa_wallet_app_internal_secret', ''));
        if ($stored !== '') {
            return $stored;
        }

        return '';
    }

    private function get_remote_aysa_user_id_for_notifications() {
        if (class_exists('Aysa_Auth_Manager')) {
            try {
                $auth = Aysa_Auth_Manager::get_instance();
                $status = $auth->is_authenticated();
                $user_id = is_array($status) ? intval($status['aysa_user_id'] ?? 0) : 0;
                if ($user_id > 0) {
                    return $user_id;
                }
            } catch (Exception $e) {
                // Fall back to email-only addressing.
            }
        }

        return 0;
    }

    private function get_notification_target_email() {
        $current_user = function_exists('wp_get_current_user') ? wp_get_current_user() : null;
        if ($current_user && !empty($current_user->user_email)) {
            $user_email = sanitize_email((string) $current_user->user_email);
            if ($user_email !== '' && is_email($user_email)) {
                return $user_email;
            }
        }

        $admin_email = sanitize_email((string) get_option('admin_email', ''));
        if ($admin_email !== '' && is_email($admin_email)) {
            return $admin_email;
        }

        return '';
    }

    private function notify_app_aysa_conversation_event($event_key, $task_label, $action_url = '', $meta = array()) {
        $event_key = sanitize_key((string) $event_key);
        $task_label = sanitize_text_field((string) $task_label);
        $internal_secret = $this->get_app_aysa_internal_secret();
        if ($event_key === '' || $task_label === '' || $internal_secret === '') {
            return false;
        }

        $payload = array(
            'event_key' => $event_key,
            'task_label' => $task_label,
            'meta' => is_array($meta) ? $meta : array(),
        );

        $user_id = $this->get_remote_aysa_user_id_for_notifications();
        if ($user_id > 0) {
            $payload['user_id'] = $user_id;
        } else {
            $email = $this->get_notification_target_email();
            if ($email !== '') {
                $payload['email'] = $email;
            }
        }

        if (!empty($action_url)) {
            $payload['action_url'] = esc_url_raw((string) $action_url);
        }

        $response = wp_remote_post($this->get_app_aysa_base_url() . '/internal/notifications/conversation-event', array(
            'timeout' => 20,
            'headers' => array(
                'Content-Type' => 'application/json',
                'Accept' => 'application/json',
                'Authorization' => 'Bearer ' . $internal_secret,
            ),
            'body' => wp_json_encode($payload),
        ));

        if (is_wp_error($response)) {
            return false;
        }

        $code = intval(wp_remote_retrieve_response_code($response));
        $body = json_decode((string) wp_remote_retrieve_body($response), true);

        return $code >= 200 && $code < 300 && is_array($body) && !empty($body['ok']);
    }

    private function aysa_adjustments_notify_pending_approval_email($project_id, $approval_key, $title, $message, $ui_action) {
        $project_id = intval($project_id);
        $approval_key = sanitize_key((string) $approval_key);
        if ($project_id <= 0 || $approval_key === '') {
            return;
        }

        $recipients = $this->aysa_adjustments_pending_approval_email_recipients();
        if ($recipients === array()) {
            return;
        }

        $site_name = wp_specialchars_decode(get_bloginfo('name'), ENT_QUOTES);
        $subject = sprintf('[AYSA] Approval required: %s', trim((string) $title) !== '' ? trim((string) $title) : 'Next SEO step');
        $action_url = $this->aysa_adjustments_approval_action_url($ui_action);
        $body_lines = array(
            'AYSA has completed a research or planning step and is waiting for approval before continuing.',
            '',
            'Project ID: ' . $project_id,
            'Approval: ' . $approval_key,
            'Title: ' . trim((string) $title),
            '',
            trim((string) $message),
            '',
            'Open in WordPress: ' . $action_url,
            '',
            'Site: ' . $site_name,
        );
        foreach ($recipients as $recipient) {
            $this->aysa_agent8_send_communication_email(
                $recipient,
                $subject,
                implode("\n", $body_lines),
                array(
                    'module' => 'adjustments',
                    'trigger' => 'approval_required',
                    'project_id' => $project_id,
                    'action_ref' => 'adjustment_approval:' . $approval_key,
                    'deep_link' => $action_url,
                    'reply_approval_supported' => false,
                )
            );
        }
    }

    private function aysa_adjustments_add_pending_approval($project_id, $approval_key, $title, $message, $ui_action = 'open_keyword_discovery') {
        $project_id = intval($project_id);
        $approval_key = sanitize_key((string) $approval_key);
        if ($project_id <= 0 || $approval_key === '') {
            return;
        }

        $items = $this->aysa_adjustments_get_pending_approvals($project_id);
        foreach ($items as $item) {
            if (!is_array($item)) {
                continue;
            }
            if ((string) ($item['approval_key'] ?? '') === $approval_key && (string) ($item['status'] ?? 'pending') === 'pending') {
                return;
            }
        }

        $items[] = array(
            'approval_key' => $approval_key,
            'title' => trim((string) $title),
            'message' => trim((string) $message),
            'ui_action' => trim((string) $ui_action),
            'status' => 'pending',
            'notified' => '0',
            'created_at' => current_time('mysql'),
        );
        $this->aysa_adjustments_save_pending_approvals($project_id, $items);
        $this->aysa_adjustments_notify_pending_approval_email($project_id, $approval_key, $title, $message, $ui_action);
        $this->notify_app_aysa_conversation_event(
            'approval_required',
            trim((string) $title) !== '' ? (string) $title : 'AYSA approval required',
            $this->aysa_adjustments_approval_action_url($ui_action),
            array(
                'project_id' => $project_id,
                'approval_key' => $approval_key,
                'resource_type' => 'project',
                'resource_id' => (string) $project_id,
                'message' => trim((string) $message),
            )
        );
    }

    private function aysa_adjustments_collect_pending_approval_notifications($project_id, $language) {
        $items = $this->aysa_adjustments_get_pending_approvals($project_id);
        if ($items === array()) {
            return array();
        }

        $notifications = array();
        $updated = array();
        foreach ($items as $item) {
            if (!is_array($item)) {
                continue;
            }
            $status = (string) ($item['status'] ?? 'pending');
            if ($status !== 'pending') {
                $updated[] = $item;
                continue;
            }

            $notified = (string) ($item['notified'] ?? '0') === '1';
            if ($notified) {
                $updated[] = $item;
                continue;
            }

            $notifications[] = array(
                'approval_key' => (string) ($item['approval_key'] ?? ''),
                'title' => (string) ($item['title'] ?? ''),
                'message' => (string) ($item['message'] ?? ''),
                'ui_action' => (string) ($item['ui_action'] ?? 'open_keyword_discovery'),
            );
            $item['notified'] = '1';
            $updated[] = $item;
        }

        $this->aysa_adjustments_save_pending_approvals($project_id, $updated);
        return $notifications;
    }

    private function aysa_get_keyword_tracking_plan($project_id) {
        $value = get_option($this->aysa_keyword_tracking_plan_option_key($project_id), array());
        return is_array($value) ? $value : array();
    }

    private function aysa_save_keyword_tracking_plan($project_id, $payload) {
        update_option(
            $this->aysa_keyword_tracking_plan_option_key($project_id),
            is_array($payload) ? $payload : array(),
            false
        );
    }

    private function aysa_adjustments_get_recent_relevant_keywords($project_id) {
        $value = get_option($this->aysa_adjustments_get_recent_relevant_keywords_option_key($project_id), array());
        return is_array($value) ? $value : array();
    }

    private function aysa_adjustments_save_recent_relevant_keywords($project_id, $payload) {
        update_option(
            $this->aysa_adjustments_get_recent_relevant_keywords_option_key($project_id),
            is_array($payload) ? $payload : array(),
            false
        );
    }

    private function aysa_adjustments_normalize_keyword_lookup_key($keyword) {
        return strtolower(trim(remove_accents((string) $keyword)));
    }

    private function aysa_adjustments_get_local_keywords_by_sources($project_id, $sources) {
        global $wpdb;
        $table = esc_sql($this->aysa_keyword_discovery_table());
        $project_id = intval($project_id);
        $sources = array_values(array_filter(array_map(function($source) {
            return strtolower(trim((string) $source));
        }, is_array($sources) ? $sources : array())));
        if ($project_id <= 0 || empty($sources)) {
            return array();
        }

        $placeholders = implode(', ', array_fill(0, count($sources), '%s'));
        $args = array_merge(array($project_id), $sources);
        $query = "SELECT keyword, search_volume, difficulty, intent, intent_probabilities, intent_source, intent_updated_at, competition, traffic, traffic_pct, position, cpc, target_url, item_types, remote_keyword_id, source, updated_at
            FROM {$table}
            WHERE project_id = %d
              AND source IN ({$placeholders})
            ORDER BY updated_at DESC, id DESC
            LIMIT 5000";
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query: table identifiers and structure are internal, and request-derived values are prepared before execution. Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $rows = $wpdb->get_results($wpdb->prepare($query, $args), ARRAY_A);
        if (!is_array($rows)) {
            return array();
        }

        $keywords = array();
        foreach ($rows as $row) {
            $item_types = $this->aysa_keyword_discovery_parse_item_types($row['item_types'] ?? array());
            $intent_probabilities = $this->aysa_keyword_discovery_normalize_intent_probabilities($row['intent_probabilities'] ?? array());
            $keywords[] = array(
                'keyword' => (string) ($row['keyword'] ?? ''),
                'searchVolume' => ($row['search_volume'] !== null ? intval($row['search_volume']) : 'N/A'),
                'difficulty' => ($row['difficulty'] !== null ? (float) $row['difficulty'] : 'N/A'),
                'intent' => ((string) ($row['intent'] ?? '') !== '' ? (string) $row['intent'] : 'N/A'),
                'intentProbabilities' => $intent_probabilities,
                'intentSource' => ((string) ($row['intent_source'] ?? '') !== '' ? (string) $row['intent_source'] : ''),
                'intentUpdatedAt' => ((string) ($row['intent_updated_at'] ?? '') !== '' ? (string) $row['intent_updated_at'] : ''),
                'competition' => ((string) ($row['competition'] ?? '') !== '' ? (string) $row['competition'] : 'N/A'),
                'traffic' => ($row['traffic'] !== null ? intval($row['traffic']) : 'N/A'),
                'trafficPct' => ($row['traffic_pct'] !== null ? floatval($row['traffic_pct']) : 'N/A'),
                'position' => ($row['position'] !== null ? floatval($row['position']) : 'N/A'),
                'cpc' => ($row['cpc'] !== null ? floatval($row['cpc']) : 'N/A'),
                'url' => ((string) ($row['target_url'] ?? '') !== '' ? (string) ($row['target_url']) : 'N/A'),
                'itemTypes' => $item_types,
                'id' => intval($row['remote_keyword_id'] ?? 0),
                'projectId' => $project_id,
                'source' => (string) ($row['source'] ?? 'suggested'),
                'updatedAt' => (string) ($row['updated_at'] ?? ''),
                'createdDate' => (string) ($row['updated_at'] ?? ''),
            );
        }

        return $keywords;
    }

    private function aysa_adjustments_build_unified_keyword_rows($project_id, $gsc_rows, $suggestion_rows, $selected_gap_rows, $association_rows = array()) {
        $project_id = intval($project_id);
        $association_rows = is_array($association_rows) ? $association_rows : array();
        $recent = $this->aysa_adjustments_get_recent_relevant_keywords($project_id);
        $recent_selected = isset($recent['selected_keywords']) && is_array($recent['selected_keywords']) ? $recent['selected_keywords'] : array();
        $selected_lookup = array_fill_keys(array_map(array($this, 'aysa_adjustments_normalize_keyword_lookup_key'), $recent_selected), true);

        $grouped_associations = $this->aysa_adjustments_group_associations_for_workspace($association_rows);
        $assoc_by_keyword = array();
        foreach ($grouped_associations as $group) {
            $keywords = isset($group['keywords']) && is_array($group['keywords']) ? $group['keywords'] : array();
            foreach ($keywords as $keyword) {
                $assoc_by_keyword[$this->aysa_adjustments_normalize_keyword_lookup_key($keyword)] = $group;
            }
        }

        $map = array();
        $add_row = function($row, $label) use (&$map, $assoc_by_keyword, $selected_lookup) {
            if (!is_array($row)) {
                return;
            }
            $keyword = trim((string) ($row['keyword'] ?? ''));
            if ($keyword === '') {
                return;
            }
            $norm = $this->aysa_adjustments_normalize_keyword_lookup_key($keyword);
            if ($norm === '') {
                return;
            }

            if (!isset($map[$norm])) {
                $map[$norm] = array(
                    'keyword' => $keyword,
                    'search_volume' => 0,
                    'difficulty' => '',
                    'position' => '',
                    'page' => '',
                    'group' => '',
                    'source_labels' => array(),
                    'source_keys' => array(),
                    'selected' => !empty($selected_lookup[$norm]) ? 'Selected' : 'Available',
                );
            }

            $entry = $map[$norm];
            $entry['source_labels'][$label] = true;
            $entry['source_keys'][strtolower($label)] = true;

            $volume = intval($row['search_volume'] ?? $row['searchVolume'] ?? 0);
            if ($volume > intval($entry['search_volume'])) {
                $entry['search_volume'] = $volume;
            }

            $difficulty = $row['difficulty'] ?? '';
            if ($entry['difficulty'] === '' && $difficulty !== '' && $difficulty !== 'N/A') {
                $entry['difficulty'] = (string) $difficulty;
            }

            $position = $row['position'] ?? '';
            if ($entry['position'] === '' && $position !== '' && $position !== 'N/A') {
                $entry['position'] = (string) $position;
            }

            $page = trim((string) ($row['target_url'] ?? $row['url'] ?? ''));
            if ($page !== '' && $page !== 'N/A' && $entry['page'] === '') {
                $entry['page'] = $page;
            }

            if (isset($assoc_by_keyword[$norm])) {
                $group = $assoc_by_keyword[$norm];
                $entry['group'] = (string) ($group['entity'] ?? $entry['group']);
                if ($entry['page'] === '' && !empty($group['page'])) {
                    $entry['page'] = (string) $group['page'];
                }
                if (!empty($selected_lookup[$norm])) {
                    $entry['selected'] = 'Selected';
                }
            }

            $map[$norm] = $entry;
        };

        foreach (is_array($gsc_rows) ? $gsc_rows : array() as $row) {
            $add_row($row, 'Google');
        }
        foreach (is_array($suggestion_rows) ? $suggestion_rows : array() as $row) {
            $add_row($row, 'Suggestions');
        }
        foreach (is_array($selected_gap_rows) ? $selected_gap_rows : array() as $row) {
            $add_row($row, 'Competitor Gap');
        }

        $rows = array_values(array_map(function($row) {
            $row['sources'] = implode(', ', array_keys($row['source_labels']));
            unset($row['source_labels'], $row['source_keys']);
            return $row;
        }, $map));

        usort($rows, function($left, $right) {
            $left_selected = (($left['selected'] ?? '') === 'Selected') ? 1 : 0;
            $right_selected = (($right['selected'] ?? '') === 'Selected') ? 1 : 0;
            if ($left_selected !== $right_selected) {
                return $right_selected <=> $left_selected;
            }
            return intval($right['search_volume'] ?? 0) <=> intval($left['search_volume'] ?? 0);
        });

        return $rows;
    }

    private function aysa_adjustments_group_associations_for_workspace($association_rows) {
        $association_rows = is_array($association_rows) ? $association_rows : array();
        $groups = array();
        foreach ($association_rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keyword = trim((string) ($row['keyword'] ?? ''));
            $associated_on = trim((string) ($row['associatedOn'] ?? $row['associated_on'] ?? ''));
            $id_intern = intval($row['idIntern'] ?? $row['id_intern'] ?? 0);
            $entity = trim((string) ($row['value'] ?? ''));
            if ($keyword === '' || $associated_on === '' || $id_intern <= 0 || $entity === '') {
                continue;
            }
            $key = strtolower($associated_on . ':' . $id_intern);
            if (!isset($groups[$key])) {
                $groups[$key] = array(
                    'type' => ucfirst($associated_on),
                    'entity' => $entity,
                    'page' => '',
                    'keywords' => array(),
                );
            }
            $groups[$key]['keywords'][$this->aysa_adjustments_normalize_keyword_lookup_key($keyword)] = $keyword;
        }

        foreach ($groups as $key => $group) {
            $parts = explode(':', $key, 2);
            $associated_on = (string) ($parts[0] ?? '');
            $id_intern = intval($parts[1] ?? 0);
            $groups[$key]['page'] = $this->aysa_monitoring_resolve_association_link($associated_on, $id_intern, $group['entity']);
            $groups[$key]['keywords'] = array_values($group['keywords']);
            sort($groups[$key]['keywords']);
        }

        return array_values($groups);
    }

    private function aysa_adjustments_get_research_snapshot($project_id, $setup_user_id = 0) {
        $keyword = $this->aysa_adjustments_get_keyword_research_snapshot($project_id, $setup_user_id);
        $monitoring_ranking = $this->aysa_adjustments_get_monitoring_ranking_snapshot($project_id);
        $competitor_gap = $this->aysa_adjustments_get_competitor_gap_snapshot($project_id);
        $competitor_identification = $this->aysa_adjustments_get_competitor_identification_snapshot($project_id);
        $competitor_backlinks = $this->aysa_adjustments_get_competitor_backlinks_snapshot($project_id);
        $offpage_backlink_audit = $this->aysa_adjustments_get_offpage_backlink_audit_snapshot($project_id);
        $offpage_link_building = $this->aysa_adjustments_get_offpage_link_building_snapshot($project_id);
        $missing_pages = $this->aysa_adjustments_get_missing_pages_snapshot($project_id);
        $content_strategy = $this->aysa_adjustments_get_content_strategy_snapshot($project_id);
        $onpage_optimization = $this->aysa_adjustments_get_onpage_optimization_snapshot($project_id);
        $technical_site_audit = $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id);
        $technical_sitemap_robots = $this->aysa_adjustments_get_technical_sitemap_robots_snapshot($project_id);
        $technical_pagespeed = $this->aysa_adjustments_get_technical_pagespeed_snapshot($project_id);
        $technical_mobile_optimization = $this->aysa_adjustments_get_technical_mobile_optimization_snapshot($project_id);
        $technical_404_errors = $this->aysa_adjustments_get_technical_404_errors_snapshot($project_id);
        $technical_301_redirects = $this->aysa_adjustments_get_technical_301_redirects_snapshot($project_id);
        $technical_410_gone = $this->aysa_adjustments_get_technical_410_gone_snapshot($project_id);
        $technical_errors = $this->aysa_adjustments_get_technical_errors_snapshot(
            $project_id,
            $technical_404_errors,
            $technical_301_redirects,
            $technical_410_gone
        );
        $technical_schema = $this->aysa_adjustments_get_cached_or_idle_technical_schema_snapshot($project_id);
        $technical_schema_markup = $this->aysa_adjustments_get_technical_schema_markup_snapshot($project_id);
        $technical_internal_linking = $this->aysa_adjustments_get_technical_internal_linking_snapshot($project_id);

        $keyword['modules'] = array(
            'keyword_research' => $keyword,
            'monitoring_ranking' => $monitoring_ranking,
            'competitor_gap' => $competitor_gap,
            'competitor_identification' => $competitor_identification,
            'competitor_backlinks' => $competitor_backlinks,
            'offpage_backlink_audit' => $offpage_backlink_audit,
            'offpage_link_building' => $offpage_link_building,
            'missing_pages' => $missing_pages,
            'content_strategy' => $content_strategy,
            'onpage_optimization' => $onpage_optimization,
            'technical_site_audit' => $technical_site_audit,
            'technical_sitemap_robots' => $technical_sitemap_robots,
            'technical_pagespeed' => $technical_pagespeed,
            'technical_mobile_optimization' => $technical_mobile_optimization,
            'technical_errors' => $technical_errors,
            'technical_404_errors' => $technical_404_errors,
            'technical_301_redirects' => $technical_301_redirects,
            'technical_410_gone' => $technical_410_gone,
            'technical_schema' => $technical_schema,
            'technical_schema_markup' => $technical_schema_markup,
            'technical_internal_linking' => $technical_internal_linking,
        );
        $keyword['overview'] = array(
            'keyword_terms' => intval($keyword['gsc_count'] ?? 0) + intval($keyword['suggestions_count'] ?? 0),
            'page_matches' => intval($keyword['associations_count'] ?? 0),
            'monitoring_keywords_tracked' => intval($monitoring_ranking['tracked_count'] ?? 0),
            'monitoring_keywords_refreshed' => intval($monitoring_ranking['refreshed_count'] ?? 0),
            'monitoring_keywords_stale' => intval($monitoring_ranking['stale_count'] ?? 0),
            'competitor_gap_keywords' => intval($competitor_gap['count'] ?? 0),
            'identified_competitors' => intval($competitor_identification['count'] ?? 0),
            'backlink_domains' => intval($competitor_backlinks['count'] ?? 0),
            'backlink_audit_items' => intval($offpage_backlink_audit['count'] ?? 0),
            'link_building_orders' => intval($offpage_link_building['orders_count'] ?? 0),
            'link_building_issues' => intval($offpage_link_building['issues_count'] ?? 0),
            'missing_pages' => intval($missing_pages['count'] ?? 0),
            'content_strategies' => intval($content_strategy['total'] ?? 0),
            'onpage_entities' => intval($onpage_optimization['count'] ?? 0),
            'onpage_optimized' => intval($onpage_optimization['optimized_count'] ?? 0),
            'technical_issues' => intval($technical_site_audit['count'] ?? 0),
            'technical_pages_crawled' => intval($technical_site_audit['pages_crawled'] ?? 0),
            'sitemap_robots_checks' => intval($technical_sitemap_robots['count'] ?? 0),
            'pagespeed_score_overall' => intval($technical_pagespeed['overall_score'] ?? 0),
            'mobile_optimization_score' => intval($technical_mobile_optimization['mobile_score'] ?? 0),
            'technical_errors_total' => intval($technical_errors['count'] ?? 0),
            'technical_errors_conflicts' => intval($technical_errors['summary']['conflicts'] ?? 0),
            'technical_404_errors' => intval($technical_404_errors['count'] ?? 0),
            'technical_301_redirects' => intval($technical_301_redirects['count'] ?? 0),
            'technical_410_gone' => intval($technical_410_gone['count'] ?? 0),
            'technical_schema_pages' => intval(($technical_schema['summary']['total_pages'] ?? 0) ?: ($technical_schema_markup['total_pages'] ?? 0)),
            'internal_linking_keywords' => intval($technical_internal_linking['count'] ?? 0),
            'internal_links_added' => intval($technical_internal_linking['total_links'] ?? 0),
        );
        $keyword['project_job_rack'] = $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state($project_id));
        $keyword['has_existing_data'] = !empty($keyword['has_existing_data'])
            || !empty($monitoring_ranking['has_data'])
            || !empty($competitor_gap['has_data'])
            || !empty($competitor_identification['has_data'])
            || !empty($competitor_backlinks['has_data'])
            || !empty($offpage_backlink_audit['has_data'])
            || !empty($offpage_link_building['has_data'])
            || !empty($missing_pages['has_data'])
            || !empty($content_strategy['has_data'])
            || !empty($onpage_optimization['has_data'])
            || !empty($technical_site_audit['has_data'])
            || !empty($technical_sitemap_robots['has_data'])
            || !empty($technical_pagespeed['has_data']);
        $keyword['has_existing_data'] = !empty($keyword['has_existing_data'])
            || !empty($technical_mobile_optimization['has_data'])
            || !empty($technical_errors['has_data'])
            || !empty($technical_404_errors['has_data'])
            || !empty($technical_301_redirects['has_data'])
            || !empty($technical_410_gone['has_data'])
            || !empty($technical_schema['has_data'])
            || !empty($technical_schema_markup['has_data'])
            || !empty($technical_internal_linking['has_data']);

        return $keyword;
    }

    private function aysa_adjustments_get_monitoring_ranking_snapshot($project_id) {
        $project_id = intval($project_id);
        $context = $this->aysa_monitoring_resolve_context('');
        $target_host = (string) ($context['target'] ?? '');
        $associations = $this->get_keyword_discovery_local_associations($project_id);
        $tracked_map = array();
        foreach ($associations as $assoc) {
            if (!is_array($assoc)) {
                continue;
            }
            $keyword = trim((string) ($assoc['keyword'] ?? ''));
            if ($keyword === '') {
                continue;
            }
            $assoc_on = (string) ($assoc['associatedOn'] ?? $assoc['associated_on'] ?? 'other');
            $id_intern = intval($assoc['idIntern'] ?? $assoc['id_intern'] ?? 0);
            $link = (string) ($assoc['link'] ?? '');
            $tracked_map[$keyword . '|' . $assoc_on . '|' . $id_intern] = array(
                'keyword' => $keyword,
                'associated_on' => $assoc_on,
                'id_intern' => $id_intern,
                'link' => $link,
            );
        }

        $rows = $this->aysa_adjustments_get_monitoring_latest_rows($project_id, $target_host);
        $rows_map = array();
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $key = trim((string) ($row['keyword'] ?? '')) . '|' . (string) ($row['associated_on'] ?? 'other') . '|' . intval($row['id_intern'] ?? 0);
            $rows_map[$key] = $row;
        }

        $stale_rows = array();
        foreach ($tracked_map as $key => $tracked_row) {
            if (!isset($rows_map[$key])) {
                $stale_rows[] = array(
                    'keyword' => (string) ($tracked_row['keyword'] ?? ''),
                    'associated_on' => (string) ($tracked_row['associated_on'] ?? ''),
                    'id_intern' => intval($tracked_row['id_intern'] ?? 0),
                    'link' => (string) ($tracked_row['link'] ?? ''),
                    'reason' => 'No local rank snapshot saved yet.',
                );
            }
        }

        $status = $this->aysa_monitoring_get_refresh_status($project_id);
        $status_rows = array(
            array('metric' => 'Tracked keywords', 'value' => count($tracked_map)),
            array('metric' => 'Keywords with local ranks', 'value' => count($rows)),
            array('metric' => 'Stale keywords', 'value' => count($stale_rows)),
            array('metric' => 'Refresh job status', 'value' => (string) ($status['status'] ?? 'idle')),
            array('metric' => 'Processed', 'value' => intval($status['processed'] ?? 0) . ' / ' . intval($status['total'] ?? 0)),
            array('metric' => 'Refreshed', 'value' => intval($status['refreshed'] ?? 0)),
            array('metric' => 'Failed', 'value' => intval($status['failed'] ?? 0)),
            array('metric' => 'Deferred', 'value' => intval($status['deferred'] ?? 0)),
        );
        $last_error = trim((string) ($status['last_error'] ?? ''));
        if ($last_error !== '') {
            $status_rows[] = array('metric' => 'Last error', 'value' => $last_error);
        }

        return array(
            'id' => 'monitoring_ranking',
            'count' => count($tracked_map),
            'tracked_count' => count($tracked_map),
            'refreshed_count' => count($rows),
            'stale_count' => count($stale_rows),
            'rows' => $rows,
            'status_rows' => $status_rows,
            'stale_rows' => $stale_rows,
            'job_status' => (string) ($status['status'] ?? 'idle'),
            'job_processed' => intval($status['processed'] ?? 0),
            'job_total' => intval($status['total'] ?? 0),
            'job_failed' => intval($status['failed'] ?? 0),
            'job_deferred' => intval($status['deferred'] ?? 0),
            'job_last_error' => $last_error,
            'target_host' => $target_host,
            'has_data' => !empty($tracked_map) || !empty($rows),
        );
    }

    private function aysa_adjustments_get_monitoring_latest_rows($project_id, $target_host) {
        global $wpdb;

        $project_id = intval($project_id);
        $target_host = (string) $target_host;
        $table = esc_sql($this->aysa_monitoring_history_table());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table));
        if ($table_exists !== $table) {
            return array();
        }

        $sql = "
            SELECT h.*
            FROM {$table} h
            INNER JOIN (
                SELECT MAX(id) AS max_id
                FROM {$table}
                WHERE project_id = %d
                  AND target_host = %s
                  AND (
                        (position IS NOT NULL AND position > 0)
                     OR (position_absolute IS NOT NULL AND position_absolute > 0)
                     OR (ctr IS NOT NULL AND ctr > 0)
                     OR (search_volume IS NOT NULL AND search_volume > 0)
                     OR (bounce_rate IS NOT NULL AND bounce_rate > 0)
                     OR (sessions IS NOT NULL AND sessions > 0)
                     OR (gsc_clicks IS NOT NULL AND gsc_clicks > 0)
                     OR (gsc_impressions IS NOT NULL AND gsc_impressions > 0)
                     OR (intent IS NOT NULL AND intent <> '')
                     OR (traffic IS NOT NULL AND traffic > 0)
                     OR (traffic_pct IS NOT NULL AND traffic_pct > 0)
                     OR (kd IS NOT NULL AND kd > 0)
                     OR (target_url IS NOT NULL AND target_url <> '')
                     OR (item_types IS NOT NULL AND item_types <> '')
                     OR (check_url IS NOT NULL AND check_url <> '')
                     OR (matched_item_type IS NOT NULL AND matched_item_type <> '')
                  )
                GROUP BY keyword, associated_on, id_intern
            ) latest ON latest.max_id = h.id
            ORDER BY h.keyword ASC
        ";
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query: table identifiers and structure are internal, and request-derived values are prepared before execution. Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $raw_rows = $wpdb->get_results($wpdb->prepare($sql, $project_id, $target_host), ARRAY_A);
        if (!is_array($raw_rows)) {
            return array();
        }

        $out = array();
        foreach ($raw_rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $latest_pos = is_null($row['position']) ? null : floatval($row['position']);
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $prev_pos = $wpdb->get_var($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT position
" .
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
                "                 FROM {$table}
                 WHERE project_id = %d
                   AND target_host = %s
                   AND keyword = %s
                   AND associated_on = %s
                   AND id_intern = %d
                 ORDER BY id DESC
                 LIMIT 1 OFFSET 1",
                $project_id,
                $target_host,
                (string) $row['keyword'],
                (string) $row['associated_on'],
                intval($row['id_intern'])
            ));
            $prev_pos = is_null($prev_pos) ? null : floatval($prev_pos);
            $delta = null;
            if (!is_null($latest_pos) && !is_null($prev_pos) && $prev_pos > 0) {
                $delta = $prev_pos - $latest_pos;
            }

            $out[] = array(
                'keyword' => (string) ($row['keyword'] ?? ''),
                'associated_on' => (string) ($row['associated_on'] ?? ''),
                'id_intern' => intval($row['id_intern'] ?? 0),
                'link' => (string) ($row['target_url'] ?? ''),
                'position' => $latest_pos,
                'previous_position' => $prev_pos,
                'position_delta' => $delta,
                'search_volume' => is_null($row['search_volume']) ? null : intval($row['search_volume']),
                'intent' => (string) ($row['intent'] ?? ''),
                'traffic' => is_null($row['traffic']) ? null : intval($row['traffic']),
                'traffic_pct' => is_null($row['traffic_pct']) ? null : floatval($row['traffic_pct']),
                'ctr' => is_null($row['ctr']) ? null : floatval($row['ctr']),
                'last_checked_at' => (string) ($row['tracked_at'] ?? ''),
            );
        }

        return $out;
    }

    private function aysa_adjustments_get_monitoring_refresh_associations($project_id) {
        $project_id = intval($project_id);
        $rows = $this->get_keyword_discovery_local_associations($project_id);
        $normalized = array();
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keyword = trim((string) ($row['keyword'] ?? ''));
            if ($keyword === '') {
                continue;
            }
            $normalized[] = array(
                'keyword' => $keyword,
                'associated_on' => (string) ($row['associatedOn'] ?? $row['associated_on'] ?? 'other'),
                'id_intern' => intval($row['idIntern'] ?? $row['id_intern'] ?? 0),
                'link' => (string) ($row['link'] ?? ''),
            );
        }
        return $this->aysa_monitoring_normalize_associations($normalized);
    }

    private function aysa_adjustments_start_monitoring_refresh($project_id) {
        $project_id = intval($project_id);
        $associations = $this->aysa_adjustments_get_monitoring_refresh_associations($project_id);
        if (empty($associations)) {
            return array(
                'ok' => false,
                'error' => 'No tracked keywords provided.',
            );
        }

        $existing_status = $this->aysa_monitoring_get_refresh_status($project_id);
        if (($existing_status['status'] ?? '') === 'running') {
            return array(
                'ok' => true,
                'already_running' => true,
                'status' => $existing_status,
            );
        }

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_monitoring_rank_refresh_start_background',
            'monitoring_rank_refresh_start_background',
            array(
                'module' => 'monitoring_analytics',
                'source' => 'adjustments_conversation',
            ),
            120,
            '/v1/dataforseo/rank-tracking/refresh-start-background'
        );

        $context = $this->aysa_monitoring_resolve_context('');
        $run_id = uniqid('mon_', true);
        $now = time();
        $queue = array(
            'run_id' => $run_id,
            'project_id' => $project_id,
            'context' => $context,
            'associations' => array_values($associations),
            'cursor' => 0,
            'batch_size' => 10,
            'current_retry' => 0,
            'created_at' => $now,
        );
        update_option($this->aysa_monitoring_refresh_queue_key($project_id), $queue, false);

        $status = array(
            'run_id' => $run_id,
            'status' => 'running',
            'total' => count($associations),
            'processed' => 0,
            'refreshed' => 0,
            'failed' => 0,
            'deferred' => 0,
            'last_error' => '',
            'started_at' => $now,
            'updated_at' => $now,
            'finished_at' => 0,
        );
        $this->aysa_monitoring_save_refresh_status($project_id, $status);
        $this->aysa_monitoring_ensure_refresh_cron_tick($project_id);
        $this->aysa_monitoring_schedule_background_refresh($project_id, 1);

        return array(
            'ok' => true,
            'started' => true,
            'status' => $status,
        );
    }

    private function aysa_adjustments_get_offpage_link_building_snapshot($project_id) {
        $project_id = intval($project_id);
        $orders = $this->aysa_get_adverlink_orders($project_id);
        $issues = get_option($this->aysa_get_adverlink_link_issues_key($project_id), array());
        $issuesLastSync = get_option($this->aysa_get_adverlink_link_issues_sync_key($project_id), array());

        if (!is_array($orders)) {
            $orders = array();
        }
        if (!is_array($issues)) {
            $issues = array();
        }
        if (!is_array($issuesLastSync)) {
            $issuesLastSync = array();
        }

        $orderRows = array();
        $statusCounts = array(
            'pending' => 0,
            'completed' => 0,
            'issues' => 0,
        );
        $subscriptionCounts = array(
            'active' => 0,
            'due' => 0,
            'renewal_issues' => 0,
        );
        $nowTs = time();
        foreach ($orders as $order) {
            if (!is_array($order)) {
                continue;
            }
            $status = strtoupper(trim((string) ($order['status'] ?? 'created')));
            $portalDomain = sanitize_text_field((string) ($order['portal_domain'] ?? ''));
            $keyword = sanitize_text_field((string) ($order['keyword'] ?? ''));
            $targetUrl = esc_url_raw((string) ($order['target_url'] ?? ''));
            $serviceType = sanitize_text_field((string) ($order['service_type'] ?? ''));
            $orderRef = sanitize_text_field((string) ($order['order_ref'] ?? ''));
            $billingType = sanitize_text_field((string) ($order['billing_type'] ?? 'one_time'));
            $verification = isset($order['verification']) && is_array($order['verification']) ? $order['verification'] : array();
            $verificationStatus = '';
            if (!empty($verification)) {
                $isValid = isset($verification['is_valid']) ? (bool) $verification['is_valid'] : null;
                if ($isValid === true) {
                    $verificationStatus = 'valid';
                } elseif ($isValid === false) {
                    $verificationStatus = 'invalid';
                }
            }

            $bucket = 'pending';
            if (in_array($status, array('COMPLETED', 'PUBLISHED', 'LIVE'), true)) {
                $bucket = 'completed';
            } elseif (in_array($status, array('FAILED', 'REJECTED', 'CANCELLED'), true) || $verificationStatus === 'invalid') {
                $bucket = 'issues';
            }
            $statusCounts[$bucket]++;

            $subscription = isset($order['subscription']) && is_array($order['subscription']) ? $order['subscription'] : array();
            $subscriptionStatus = sanitize_text_field((string) ($subscription['status'] ?? ''));
            $nextRenewalAt = sanitize_text_field((string) ($subscription['next_renewal_at'] ?? ''));
            $renewalCount = intval($order['renewal_count'] ?? 0);
            $renewalError = sanitize_text_field((string) ($order['last_subscription_renewal_error'] ?? ''));
            $nextRenewalTs = $this->aysa_subscription_next_renewal_timestamp($order);
            if ($this->aysa_is_subscription_order($order)) {
                if (strtolower($subscriptionStatus) === 'active') {
                    $subscriptionCounts['active']++;
                }
                if ($nextRenewalTs > 0 && $nextRenewalTs <= $nowTs) {
                    $subscriptionCounts['due']++;
                }
                if ($renewalError !== '') {
                    $subscriptionCounts['renewal_issues']++;
                }
            }

            $orderRows[] = array(
                'order_ref' => $orderRef,
                'portal_domain' => $portalDomain,
                'service_type' => $serviceType,
                'billing_type' => $billingType,
                'keyword' => $keyword,
                'target_url' => $targetUrl,
                'status' => $status,
                'verification' => $verificationStatus,
                'subscription_status' => $subscriptionStatus,
                'next_renewal_at' => $nextRenewalAt,
                'renewal_count' => $renewalCount,
                'renewal_error' => $renewalError,
                'created_at' => sanitize_text_field((string) ($order['created_at'] ?? '')),
                'charged_credits_cents' => intval($order['charged_credits_cents'] ?? 0),
            );
        }

        usort($orderRows, static function(array $a, array $b): int {
            return strcmp((string) ($b['created_at'] ?? ''), (string) ($a['created_at'] ?? ''));
        });

        $issueRows = array();
        foreach ($issues as $item) {
            if (!is_array($item)) {
                continue;
            }
            $issueRows[] = array(
                'portal_domain' => sanitize_text_field((string) ($item['portal_domain'] ?? $item['domain'] ?? '')),
                'target_url' => esc_url_raw((string) ($item['target_url'] ?? '')),
                'live_url' => esc_url_raw((string) ($item['live_url'] ?? $item['url'] ?? '')),
                'issue_type' => sanitize_text_field((string) ($item['issue_type'] ?? '')),
                'status' => sanitize_text_field((string) ($item['status'] ?? '')),
                'message' => sanitize_text_field((string) ($item['message'] ?? $item['issue_message'] ?? '')),
                'order_ref' => sanitize_text_field((string) ($item['order_ref'] ?? '')),
                'updated_at' => sanitize_text_field((string) ($item['updated_at'] ?? '')),
            );
        }

        usort($issueRows, static function(array $a, array $b): int {
            return strcmp((string) ($b['updated_at'] ?? ''), (string) ($a['updated_at'] ?? ''));
        });

        $overviewRows = array(
            array('metric' => 'Orders tracked', 'value' => count($orderRows)),
            array('metric' => 'Pending / in progress', 'value' => intval($statusCounts['pending'])),
            array('metric' => 'Completed / live', 'value' => intval($statusCounts['completed'])),
            array('metric' => 'Orders with issues', 'value' => intval($statusCounts['issues'])),
            array('metric' => 'Link issues detected', 'value' => count($issueRows)),
            array('metric' => 'Monthly subscriptions active', 'value' => intval($subscriptionCounts['active'])),
            array('metric' => 'Monthly renewals due', 'value' => intval($subscriptionCounts['due'])),
            array('metric' => 'Monthly renewal issues', 'value' => intval($subscriptionCounts['renewal_issues'])),
        );

        return array(
            'id' => 'offpage_link_building',
            'count' => count($orderRows) + count($issueRows),
            'orders_count' => count($orderRows),
            'issues_count' => count($issueRows),
            'pending_orders' => intval($statusCounts['pending']),
            'completed_orders' => intval($statusCounts['completed']),
            'problem_orders' => intval($statusCounts['issues']),
            'subscriptions_active_count' => intval($subscriptionCounts['active']),
            'subscriptions_due_count' => intval($subscriptionCounts['due']),
            'subscriptions_issues_count' => intval($subscriptionCounts['renewal_issues']),
            'rows' => $orderRows,
            'orders_rows' => $orderRows,
            'issues_rows' => $issueRows,
            'overview_rows' => $overviewRows,
            'last_sync' => $issuesLastSync,
            'has_data' => !empty($orderRows) || !empty($issueRows),
        );
    }

    private function aysa_adjustments_get_onpage_optimization_snapshot($project_id) {
        $service = $this->aysa_onpage_workspace_service();
        if (!$service) {
            return array(
                'id' => 'onpage_optimization',
                'count' => 0,
                'rows' => array(),
                'optimized_count' => 0,
                'needs_work_count' => 0,
                'not_optimized_count' => 0,
                'top_types' => array(),
                'has_data' => false,
            );
        }

        $project_id = intval($project_id);
        $workspace = $service->build_workspace($project_id, array(
            'entity_group' => 'page',
            'page' => 1,
            'per_page' => 500,
            'refresh' => false,
        ));
        $summary = is_array($workspace['summary'] ?? null) ? $workspace['summary'] : array();

        $rows = array();
        $type_counts = array();
        foreach (array('page', 'article', 'ecommerce_category', 'product', 'other') as $group) {
            $group_workspace = $service->build_workspace($project_id, array(
                'entity_group' => $group,
                'page' => 1,
                'per_page' => 250,
            ));
            foreach ((array) ($group_workspace['rows'] ?? array()) as $row) {
                $group_label = match ((string) ($row['entity_group'] ?? 'other')) {
                    'page' => 'Page',
                    'article' => 'Article',
                    'ecommerce_category' => 'Ecommerce Category',
                    'product' => 'Product',
                    default => 'Other',
                };
                $type_counts[$group_label] = intval($type_counts[$group_label] ?? 0) + 1;
                $rows[] = array(
                    'entity_id' => intval($row['entity_id'] ?? 0),
                    'entity_kind' => (string) ($row['object_kind'] ?? ''),
                    'entity_processor_type' => $group_label,
                    'entity_type' => $group_label,
                    'entity' => (string) ($row['title'] ?? ''),
                    'main_keyword' => (string) ($row['main_keyword'] ?? ''),
                    'all_keywords' => (string) ($row['main_keyword'] ?? ''),
                    'optimized' => ((string) ($row['seo_state'] ?? '') === 'optimized') ? 'Optimized' : 'Not optimized',
                    'seo_score' => isset($row['seo_score']) && is_numeric($row['seo_score']) ? (string) intval($row['seo_score']) . '/100' : 'N/A',
                    'page' => (string) ($row['url'] ?? ''),
                    'edit_url' => '',
                );
            }
        }

        $top_types = array();
        foreach ($type_counts as $label => $count) {
            $top_types[] = array(
                'label' => (string) $label,
                'count' => intval($count),
            );
        }

        return array(
            'id' => 'onpage_optimization',
            'count' => intval($summary['all'] ?? count($rows)),
            'rows' => $rows,
            'optimized_count' => intval($summary['optimized'] ?? 0),
            'needs_work_count' => 0,
            'not_optimized_count' => intval($summary['not_optimized'] ?? 0),
            'top_types' => array_slice($top_types, 0, 5),
            'has_data' => !empty($rows),
        );
    }

    private function aysa_adjustments_get_onpage_entity_quality($kind, $entity_id) {
        if ($kind === 'term') {
            return array(
                'seo_score' => get_term_meta($entity_id, '_aysa_entity_seo_score_cache', true),
                'content_source' => get_term_meta($entity_id, '_aysa_entity_content_source_cache', true),
            );
        }

        return array(
            'seo_score' => get_post_meta($entity_id, '_aysa_entity_seo_score_cache', true),
            'content_source' => get_post_meta($entity_id, '_aysa_entity_content_source_cache', true),
        );
    }

    private function aysa_adjustments_get_onpage_optimized_status($score, $content_source) {
        $content_source = strtolower(trim((string) $content_source));
        $score = is_numeric($score) ? intval($score) : null;
        if ($score !== null && $score >= 80) {
            return 'Optimized';
        }
        if ($score !== null && $score > 0) {
            return 'Needs work';
        }
        if ($content_source === 'aysa_generated') {
            return 'Optimized';
        }
        return 'Not optimized';
    }

    private function aysa_adjustments_get_onpage_entity_type_label($kind, $entity_id) {
        if ($kind === 'term') {
            $term = get_term($entity_id);
            if ($term && !is_wp_error($term)) {
                return match ((string) ($term->taxonomy ?? '')) {
                    'product_cat' => 'Category',
                    'category' => 'Article Category',
                    default => 'Term',
                };
            }
            return 'Term';
        }

        return match ((string) get_post_type($entity_id)) {
            'product' => 'Product',
            'page' => 'Page',
            'post' => 'Article',
            default => 'Post',
        };
    }

    private function aysa_adjustments_get_onpage_entity_title($kind, $entity_id) {
        if ($kind === 'term') {
            $term = get_term($entity_id);
            return ($term && !is_wp_error($term)) ? (string) $term->name : ('Term #' . intval($entity_id));
        }

        $post = get_post($entity_id);
        if ($post instanceof WP_Post) {
            $title = trim((string) get_the_title($post));
            return $title !== '' ? $title : ('Post #' . intval($entity_id));
        }
        return 'Post #' . intval($entity_id);
    }

    private function aysa_adjustments_get_onpage_entity_processor_type($kind, $entity_id) {
        if ($kind === 'term') {
            $term = get_term($entity_id);
            if ($term && !is_wp_error($term)) {
                return match ((string) ($term->taxonomy ?? '')) {
                    'product_cat', 'category' => 'Category',
                    default => 'Category',
                };
            }
            return 'Category';
        }

        return match ((string) get_post_type($entity_id)) {
            'product' => 'Product',
            'page' => 'Page',
            'post' => 'Post',
            default => 'Post',
        };
    }

    private function aysa_adjustments_get_onpage_entity_edit_url($kind, $entity_id) {
        $entity_id = intval($entity_id);
        if ($entity_id <= 0) {
            return '';
        }

        if ($kind === 'term') {
            $term = get_term($entity_id);
            if (!$term || is_wp_error($term)) {
                return '';
            }
            $taxonomy = (string) ($term->taxonomy ?? '');
            if ($taxonomy === '') {
                return '';
            }
            return (string) admin_url('term.php?taxonomy=' . rawurlencode($taxonomy) . '&tag_ID=' . $entity_id);
        }

        return (string) get_edit_post_link($entity_id, 'raw');
    }

    private function aysa_adjustments_get_onpage_entity_url($kind, $entity_id) {
        if ($kind === 'term') {
            $link = get_term_link(intval($entity_id));
            return is_wp_error($link) ? '' : (string) $link;
        }
        $link = get_permalink(intval($entity_id));
        return is_string($link) ? $link : '';
    }

    private function aysa_adjustments_reduce_research_snapshot_for_ai($snapshot) {
        if (!is_array($snapshot)) {
            return array();
        }
        $reduced = $snapshot;
        foreach (array('gsc_rows', 'suggestion_rows', 'association_rows') as $bucket) {
            if (isset($reduced[$bucket]) && is_array($reduced[$bucket])) {
                $reduced[$bucket] = array_slice(array_values($reduced[$bucket]), 0, 20);
            }
        }
        if (isset($reduced['modules']) && is_array($reduced['modules'])) {
            foreach ($reduced['modules'] as $moduleId => $module) {
                if (!is_array($module)) {
                    continue;
                }
                if (isset($module['rows']) && is_array($module['rows'])) {
                    $reduced['modules'][$moduleId]['rows'] = array_slice(array_values($module['rows']), 0, 20);
                }
            }
        }
        return $reduced;
    }

    private function aysa_adjustments_get_competitor_gap_snapshot($project_id) {
        $cached = $this->aysa_competitor_gap_get_cached_payload($project_id);
        $data = is_array($cached) && isset($cached['data']) && is_array($cached['data']) ? $cached['data'] : array();
        $ranked_keywords = isset($data['rankedKeywords']) && is_array($data['rankedKeywords']) ? array_values($data['rankedKeywords']) : array();
        $search_volumes = isset($data['searchVolume']) && is_array($data['searchVolume']) ? $data['searchVolume'] : array();
        $keyword_metrics = isset($data['keywordMetrics']) && is_array($data['keywordMetrics']) ? $data['keywordMetrics'] : array();

        $rows = array();
        foreach ($ranked_keywords as $row) {
            $keyword = '';
            $search_volume = 0;
            $difficulty = 0;
            $intent = '';

            if (is_array($row)) {
                $keyword = sanitize_text_field((string) ($row['keyword'] ?? $row['query'] ?? ''));
                $search_volume = intval($row['searchVolume'] ?? $row['search_volume'] ?? 0);
                $difficulty = intval($row['difficulty'] ?? $row['keyword_difficulty'] ?? 0);
                $intent = sanitize_text_field((string) ($row['intent'] ?? ''));
            } else {
                $keyword = sanitize_text_field((string) $row);
            }

            if ($keyword === '') {
                continue;
            }

            if ($search_volume <= 0 && isset($search_volumes[$keyword])) {
                $search_volume = intval($search_volumes[$keyword]);
            }

            if (isset($keyword_metrics[$keyword]) && is_array($keyword_metrics[$keyword])) {
                $metric = $keyword_metrics[$keyword];
                if ($search_volume <= 0) {
                    $search_volume = intval($metric['search_volume'] ?? 0);
                }
                if ($difficulty <= 0) {
                    $difficulty = intval(round(floatval($metric['kd'] ?? $metric['difficulty'] ?? 0)));
                }
                if ($intent === '') {
                    $intent = sanitize_text_field((string) ($metric['intent'] ?? ($metric['search_intent_info']['main_intent'] ?? '')));
                }
            }

            $rows[] = array(
                'keyword' => $keyword,
                'search_volume' => $search_volume,
                'difficulty' => $difficulty,
                'intent' => $intent,
                'source' => 'competitor_gap',
            );
        }

        return array(
            'id' => 'competitor_gap',
            'count' => count($rows),
            'rows' => $rows,
            'cached_at' => intval($cached['cached_at'] ?? 0),
            'has_data' => !empty($rows),
        );
    }

    private function aysa_adjustments_get_competitor_identification_snapshot($project_id) {
        $cached = $this->aysa_competitor_identification_get_cached_payload($project_id);
        $data = is_array($cached) && isset($cached['data']) && is_array($cached['data']) ? $cached['data'] : array();
        $active_rows = isset($data['active_competitors']) && is_array($data['active_competitors']) ? array_values($data['active_competitors']) : array();
        $suggested_rows = isset($data['suggested_competitors']) && is_array($data['suggested_competitors']) ? array_values($data['suggested_competitors']) : array();
        $rows = array_merge($active_rows, $suggested_rows);
        $rows = array_map(function($row) {
            if (!is_array($row)) {
                return array();
            }
            return array(
                'domain' => sanitize_text_field((string) ($row['domain'] ?? $row['competitor'] ?? '')),
                'overlap_keywords' => intval($row['overlap_keywords'] ?? $row['keywords_overlap'] ?? 0),
                'keyword_hits' => intval($row['keyword_hits'] ?? 0),
                'avg_position' => isset($row['avg_position']) ? round(floatval($row['avg_position']), 2) : '',
                'competitor_status' => sanitize_key((string) ($row['competitor_status'] ?? 'suggested')),
                'status_reason' => sanitize_text_field((string) ($row['status_reason'] ?? '')),
                'source' => 'competitor_identification',
            );
        }, $rows);

        return array(
            'id' => 'competitor_identification',
            'count' => count($rows),
            'rows' => $rows,
            'cached_at' => intval($cached['cached_at'] ?? 0),
            'has_data' => !empty($rows),
            'active_count' => count($active_rows),
            'suggested_count' => count($suggested_rows),
        );
    }

    private function aysa_adjustments_get_competitor_backlinks_snapshot($project_id) {
        $summary = get_option('aysa_backlink_summary_' . intval($project_id), array());
        $items = is_array($summary) && isset($summary['items']) && is_array($summary['items']) ? array_values($summary['items']) : array();
        $home_host = $this->aysa_monitoring_extract_host((string) home_url('/'));
        $resolved_context = $this->aysa_monitoring_resolve_context($home_host);
        $target_host = strtolower(trim((string) ($resolved_context['target'] ?? '')));
        if ($target_host !== '') {
            $target_host = $this->aysa_monitoring_extract_host($target_host);
        }
        if ($target_host === '') {
            $target_host = $home_host;
        }
        $target_host = strtolower(preg_replace('/^www\./', '', (string) $target_host));
        $rows = array_map(function($row) {
            if (!is_array($row)) {
                return array();
            }
            return array(
                'domain' => sanitize_text_field((string) ($row['domain'] ?? '')),
                'authority' => isset($row['authority'])
                    ? round(floatval($row['authority']), 2)
                    : (isset($row['avg_authority']) ? round(floatval($row['avg_authority']), 2) : ''),
                'backlinks' => intval($row['backlinks'] ?? $row['links'] ?? $row['total_backlinks'] ?? 0),
                'source' => 'competitor_backlinks',
            );
        }, $items);
        $rows = array_values(array_filter($rows, function($row) use ($target_host) {
            if (!is_array($row)) {
                return false;
            }
            $domain = strtolower(preg_replace('/^www\./', '', trim((string) ($row['domain'] ?? ''))));
            if ($domain === '') {
                return false;
            }
            return $target_host === '' || $domain !== $target_host;
        }));

        return array(
            'id' => 'competitor_backlinks',
            'count' => count($rows),
            'rows' => $rows,
            'cached_at' => strtotime((string) ($summary['generated_at'] ?? '')) ?: 0,
            'has_data' => !empty($rows),
            'target_profile' => (isset($summary['target_profile']) && is_array($summary['target_profile'])) ? $summary['target_profile'] : array(),
            'competitor_profiles' => (isset($summary['competitor_profiles']) && is_array($summary['competitor_profiles'])) ? $summary['competitor_profiles'] : array(),
            'benchmark' => (isset($summary['benchmark']) && is_array($summary['benchmark'])) ? $summary['benchmark'] : array(),
            'gap' => (isset($summary['gap']) && is_array($summary['gap'])) ? $summary['gap'] : array(),
            'brand_terms' => (isset($summary['brand_terms']) && is_array($summary['brand_terms'])) ? $summary['brand_terms'] : array(),
        );
    }

    private function aysa_adjustments_get_offpage_backlink_audit_snapshot($project_id) {
        $project_id = intval($project_id);
        $snapshot = get_option($this->aysa_get_backlink_audit_key($project_id), array());
        $last_sync = get_option($this->aysa_get_backlink_audit_sync_key($project_id), array());
        $items = (is_array($snapshot) && isset($snapshot['items']) && is_array($snapshot['items'])) ? array_values($snapshot['items']) : array();

        $rows = array();
        $refDomains = array();
        $followCount = 0;
        $brokenCount = 0;
        $riskCounts = array(
            'low' => 0,
            'medium' => 0,
            'high' => 0,
        );

        foreach ($items as $item) {
            if (!is_array($item)) {
                continue;
            }
            $sourceUrl = esc_url_raw((string) ($item['source_url'] ?? $item['source'] ?? ''));
            $targetUrl = esc_url_raw((string) ($item['target_url'] ?? $item['target'] ?? ''));
            $anchorText = sanitize_text_field((string) ($item['anchor_text'] ?? $item['anchor'] ?? ''));
            $referringDomain = sanitize_text_field((string) ($item['referring_domain'] ?? $item['domain'] ?? ''));
            $follow = strtolower(trim((string) ($item['follow'] ?? $item['follow_attr'] ?? $item['link_attr'] ?? '')));
            $health = strtolower(trim((string) ($item['health'] ?? $item['status'] ?? '')));
            $risk = strtolower(trim((string) ($item['risk'] ?? $item['risk_level'] ?? '')));
            $issueType = sanitize_text_field((string) ($item['issue_type'] ?? ''));
            $issueMessage = sanitize_text_field((string) ($item['issue_message'] ?? $item['issue_summary'] ?? ''));
            $firstSeen = sanitize_text_field((string) ($item['first_seen'] ?? ''));
            $lastSeen = sanitize_text_field((string) ($item['last_seen'] ?? ''));

            if ($risk === '' && $health === 'broken') {
                $risk = 'high';
            }
            if (!isset($riskCounts[$risk])) {
                $risk = '';
            }

            if ($referringDomain !== '') {
                $refDomains[strtolower($referringDomain)] = true;
            }
            if ($follow === 'follow') {
                $followCount++;
            }
            if ($health === 'broken' || $issueType === 'broken' || str_contains(strtolower($issueMessage), 'broken')) {
                $brokenCount++;
            }
            if ($risk !== '') {
                $riskCounts[$risk]++;
            }

            $rows[] = array(
                'referring_domain' => $referringDomain,
                'source_url' => $sourceUrl,
                'target_url' => $targetUrl,
                'anchor_text' => $anchorText,
                'risk' => $risk !== '' ? ucfirst($risk) : '',
                'follow' => $follow,
                'health' => $health,
                'issue_type' => $issueType,
                'issue_message' => $issueMessage,
                'first_seen' => $firstSeen,
                'last_seen' => $lastSeen,
            );
        }

        usort($rows, static function(array $a, array $b): int {
            $riskWeight = static function(string $value): int {
                $value = strtolower(trim($value));
                return match ($value) {
                    'high' => 3,
                    'medium' => 2,
                    'low' => 1,
                    default => 0,
                };
            };
            $riskCmp = $riskWeight((string) ($b['risk'] ?? '')) <=> $riskWeight((string) ($a['risk'] ?? ''));
            if ($riskCmp !== 0) {
                return $riskCmp;
            }
            return strcasecmp((string) ($a['referring_domain'] ?? ''), (string) ($b['referring_domain'] ?? ''));
        });

        $count = count($rows);
        return array(
            'id' => 'offpage_backlink_audit',
            'count' => $count,
            'rows' => $rows,
            'referring_domains' => count($refDomains),
            'follow_ratio' => $count > 0 ? round(($followCount / $count) * 100, 2) : 0,
            'broken_count' => $brokenCount,
            'risk_low' => intval($riskCounts['low']),
            'risk_medium' => intval($riskCounts['medium']),
            'risk_high' => intval($riskCounts['high']),
            'provider' => sanitize_text_field((string) ($snapshot['provider'] ?? '')),
            'generated_at' => sanitize_text_field((string) ($snapshot['generated_at'] ?? '')),
            'last_sync' => is_array($last_sync) ? $last_sync : array(),
            'has_data' => !empty($rows),
        );
    }

    private function aysa_adjustments_get_missing_pages_snapshot($project_id) {
        global $wpdb;

        $rows = array();
        $keyword_table = esc_sql($this->aysa_keyword_discovery_table());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $keyword_table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $keyword_table));
        if ($keyword_table_exists === $keyword_table) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $keyword_rows = $wpdb->get_results($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT keyword, source, search_volume, difficulty, target_url
" .
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
                "                 FROM {$keyword_table}
                 WHERE project_id = %d
                   AND (target_url IS NULL OR target_url = '')
                 ORDER BY search_volume DESC, keyword ASC",
                $project_id
            ), ARRAY_A);
            if (is_array($keyword_rows)) {
                foreach ($keyword_rows as $row) {
                    $keyword = sanitize_text_field((string) ($row['keyword'] ?? ''));
                    if ($keyword === '') {
                        continue;
                    }
                    $rows[] = array(
                        'keyword' => $keyword,
                        'source' => sanitize_text_field((string) ($row['source'] ?? 'missing_pages')),
                        'search_volume' => intval($row['search_volume'] ?? 0),
                        'difficulty' => intval($row['difficulty'] ?? 0),
                        'target' => '',
                    );
                }
            }
        }

        $strategy_table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $strategy_table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $strategy_table));
        if ($strategy_table_exists === $strategy_table) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $strategy_rows = $wpdb->get_results($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT title, keywords, intent, recommended_page_type
" .
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
                "                 FROM {$strategy_table}
                 WHERE project_id = %d
                   AND strategy_type = 'missing_pages'
                   AND (associated_page IS NULL OR associated_page = '')",
                $project_id
            ), ARRAY_A);
            if (is_array($strategy_rows)) {
                foreach ($strategy_rows as $row) {
                    $keyword = $this->aysa_missing_page_extract_keyword((string) ($row['title'] ?? ''), (string) ($row['keywords'] ?? ''));
                    if ($keyword === '') {
                        continue;
                    }
                    $rows[] = array(
                        'keyword' => $keyword,
                        'source' => 'missing_pages_plan',
                        'search_volume' => 0,
                        'difficulty' => 0,
                        'target' => sanitize_text_field((string) ($row['recommended_page_type'] ?? 'Landing Page')),
                    );
                }
            }
        }

        return array(
            'id' => 'missing_pages',
            'count' => count($rows),
            'rows' => $rows,
            'has_data' => !empty($rows),
        );
    }

    private function aysa_adjustments_get_content_strategy_snapshot($project_id) {
        global $wpdb;
        $table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if ($table_exists !== $table) {
            return array(
                'id' => 'content_strategy',
                'total' => 0,
                'generated' => 0,
                'published' => 0,
                'pending' => 0,
                'published_today_count' => 0,
                'published_today' => array(),
                'rows' => array(),
                'has_data' => false,
            );
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $rows = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT id, title, strategy_type, status, associated_page, execution_stage, post_id
" .
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
            "             FROM {$table}
             WHERE project_id = %d
               AND COALESCE(automation_status, '') <> 'deleted'
               AND duplicate_of_strategy_id IS NULL
               AND superseded_by_strategy_id IS NULL
             ORDER BY id DESC",
            $project_id
        ), ARRAY_A);
        $total = 0;
        $generated = 0;
        $published = 0;
        $published_today = array();
        $today_start = current_datetime()->setTime(0, 0, 0);
        $today_end = (clone $today_start)->modify('+1 day');
        $items = array();
        if (is_array($rows)) {
            foreach ($rows as $row) {
                $total++;
                $status = strtolower((string) ($row['status'] ?? ''));
                if ($status === 'generated') {
                    $generated++;
                }
                if (trim((string) ($row['associated_page'] ?? '')) !== '') {
                    $published++;
                }

                $post_id = isset($row['post_id']) ? intval($row['post_id']) : 0;
                if ($post_id > 0) {
                    $post = get_post($post_id);
                    if ($post && !is_wp_error($post) && get_post_status($post_id) === 'publish') {
                        $post_ts = strtotime((string) $post->post_date);
                        if ($post_ts && $post_ts >= $today_start->getTimestamp() && $post_ts < $today_end->getTimestamp()) {
                            $published_today[] = array(
                                'title' => sanitize_text_field(get_the_title($post_id)),
                                'url' => esc_url_raw((string) get_permalink($post_id)),
                                'time' => wp_date('H:i', $post_ts),
                                'post_id' => $post_id,
                            );
                        }
                    }
                }

                $items[] = array(
                    'title' => sanitize_text_field((string) ($row['title'] ?? '')),
                    'strategy_type' => sanitize_key((string) ($row['strategy_type'] ?? '')),
                    'status' => sanitize_text_field((string) ($row['status'] ?? '')),
                    'stage' => sanitize_text_field((string) ($row['execution_stage'] ?? '')),
                );
            }
        }

        return array(
            'id' => 'content_strategy',
            'total' => $total,
            'generated' => $generated,
            'published' => $published,
            'pending' => max(0, $total - $generated),
            'published_today_count' => count($published_today),
            'published_today' => array_slice($published_today, 0, 20),
            'rows' => $items,
            'has_data' => !empty($items),
        );
    }

    private function aysa_adjustments_get_technical_site_audit_snapshot($project_id, $include_action_bundles = true) {
        $project_id = $this->aysa_resolve_technical_project_id(intval($project_id));
        $state = get_option($this->aysa_technical_site_audit_state_key($project_id), array());
        if (!is_array($state)) {
            $state = array();
        }
        if (empty($state)) {
            $latest_version = $this->aysa_sf_site_audit_get_latest_version_row($project_id);
            if (is_array($latest_version) && !empty($latest_version)) {
                $state = $this->aysa_sf_site_audit_build_state_from_version_row($latest_version);
            }
        }

        $audit = !empty($state['audit_snapshot']) && is_array($state['audit_snapshot']) ? $state['audit_snapshot'] : array();
        $overview = !empty($audit['overview']) && is_array($audit['overview']) ? $audit['overview'] : array();
        $issues = !empty($audit['issues']) && is_array($audit['issues']) ? array_values($audit['issues']) : array();
        $pages = !empty($audit['pages']) && is_array($audit['pages']) ? array_values($audit['pages']) : array();
        $statistics = !empty($audit['statistics']) && is_array($audit['statistics']) ? $audit['statistics'] : array();
        $files = !empty($state['result_files']) && is_array($state['result_files']) ? array_values($state['result_files']) : array();
        $history_count = $this->aysa_sf_site_audit_version_history_count($project_id);
        $pages_crawled = intval($overview['pages_crawled'] ?? 0);
        $csv_file_count = 0;
        foreach ($files as $file) {
            if (!is_array($file)) {
                continue;
            }
            $filename = strtolower((string) ($file['filename'] ?? ''));
            $type = strtolower((string) ($file['type'] ?? ''));
            if ($type === 'csv' || substr($filename, -4) === '.csv') {
                $csv_file_count++;
            }
        }
        $valid_crawl = $pages_crawled > 0 && $csv_file_count > 0;
        $snapshot_status = sanitize_key((string) ($state['status'] ?? 'idle'));
        if (!$valid_crawl && in_array($snapshot_status, array('completed', 'complete', 'success', 'succeeded', 'ready'), true)) {
            $snapshot_status = 'invalid';
        }
        $issue_lifecycle = $this->aysa_technical_site_audit_get_issue_lifecycle_map($project_id);

        $issue_rows = array();
        foreach ($issues as $row) {
            $normalized_issue = $this->aysa_adjustments_normalize_technical_site_audit_issue($row, $overview, $issue_lifecycle);
            if (!empty($normalized_issue)) {
                $issue_rows[] = $normalized_issue;
            }
        }

        $page_rows = array_map(function($row) {
            if (!is_array($row)) {
                return array();
            }
            return array(
                'url' => esc_url_raw((string) ($row['url'] ?? '')),
                'title' => sanitize_text_field((string) ($row['title'] ?? '')),
                'status_code' => intval($row['status_code'] ?? 0),
                'indexability' => sanitize_text_field((string) ($row['indexability'] ?? '')),
                'crawl_depth' => intval($row['crawl_depth'] ?? 0),
                'inlinks' => intval($row['inlinks'] ?? 0),
            );
        }, array_slice($pages, 0, 300));

        $file_rows = array_map(function($row) {
            if (!is_array($row)) {
                return array();
            }
            return array(
                'filename' => sanitize_text_field((string) ($row['filename'] ?? '')),
                'path' => sanitize_text_field((string) ($row['path'] ?? '')),
                'type' => sanitize_text_field((string) ($row['type'] ?? '')),
                'row_count' => intval($row['row_count'] ?? 0),
                'columns' => isset($row['columns']) && is_array($row['columns'])
                    ? array_values(array_map('sanitize_text_field', $row['columns']))
                    : array(),
                'column_count' => isset($row['columns']) && is_array($row['columns']) ? count($row['columns']) : 0,
                'full_rows_available' => !empty($row['full_rows_available']),
                'row_access_method' => sanitize_text_field((string) ($row['row_access_method'] ?? '')),
                'updated_at' => sanitize_text_field((string) ($row['updated_at'] ?? '')),
                'size_bytes' => intval($row['size_bytes'] ?? 0),
            );
        }, $files);

        $overview_rows = array(
            array(
                'metric' => 'Pages crawled',
                'value' => (string) intval($overview['pages_crawled'] ?? 0),
            ),
            array(
                'metric' => 'Audit mode',
                'value' => sanitize_text_field((string) ($state['mode'] ?? 'baseline_full')),
            ),
            array(
                'metric' => 'Site health',
                'value' => (string) intval($overview['site_health'] ?? 0) . '%',
            ),
            array(
                'metric' => 'Errors',
                'value' => (string) intval($overview['errors'] ?? 0),
            ),
            array(
                'metric' => 'Warnings',
                'value' => (string) intval($overview['warnings'] ?? 0),
            ),
            array(
                'metric' => 'Indexable pages',
                'value' => (string) intval($overview['indexable_pages'] ?? 0),
            ),
            array(
                'metric' => 'Non-indexable pages',
                'value' => (string) intval($overview['non_indexable_pages'] ?? 0),
            ),
            array(
                'metric' => 'Pages over depth 3',
                'value' => (string) intval($overview['deep_pages_over_3'] ?? 0),
            ),
            array(
                'metric' => 'Pages with <=1 inlink',
                'value' => (string) intval($overview['single_inlink_pages'] ?? 0),
            ),
        );

        $export_rows = array();
        $exports = !empty($statistics['exports']) && is_array($statistics['exports']) ? $statistics['exports'] : array();
        foreach ($exports as $name => $count) {
            $label = ucwords(str_replace(array('_', '-'), ' ', sanitize_text_field((string) $name)));
            $export_rows[] = array(
                'export' => $label,
                'rows' => (string) intval($count),
            );
        }
        $export_registry = !empty($audit['export_registry']) && is_array($audit['export_registry'])
            ? array_values($audit['export_registry'])
            : array();
        $export_registry_rows = array();
        foreach ($export_registry as $registry_row) {
            if (!is_array($registry_row)) {
                continue;
            }
            $columns = isset($registry_row['column_list']) && is_array($registry_row['column_list'])
                ? array_values(array_map('sanitize_text_field', $registry_row['column_list']))
                : array();
            $export_registry_rows[] = array(
                'audit_run_id' => sanitize_text_field((string) ($registry_row['audit_run_id'] ?? ($state['run_id'] ?? ''))),
                'export_key' => sanitize_key((string) ($registry_row['export_key'] ?? '')),
                'source_name' => sanitize_text_field((string) ($registry_row['source_name'] ?? '')),
                'filename' => sanitize_text_field((string) ($registry_row['filename'] ?? '')),
                'file_path' => sanitize_text_field((string) ($registry_row['file_path'] ?? '')),
                'row_count' => intval($registry_row['row_count'] ?? 0),
                'column_list' => $columns,
                'column_count' => intval($registry_row['column_count'] ?? count($columns)),
                'file_type' => sanitize_text_field((string) ($registry_row['file_type'] ?? '')),
                'extracted_status' => sanitize_key((string) ($registry_row['extracted_status'] ?? '')),
                'normalized_status' => sanitize_key((string) ($registry_row['normalized_status'] ?? '')),
                'usage_status' => sanitize_key((string) ($registry_row['usage_status'] ?? 'ignored')),
                'full_rows_available' => !empty($registry_row['full_rows_available']),
                'row_access_method' => sanitize_text_field((string) ($registry_row['row_access_method'] ?? '')),
                'size_bytes' => intval($registry_row['size_bytes'] ?? 0),
                'updated_at' => sanitize_text_field((string) ($registry_row['updated_at'] ?? '')),
            );
        }

        $action_bundles = array();
        if ($include_action_bundles) {
            $action_bundles = $this->aysa_adjustments_build_technical_site_audit_action_bundles(
                $project_id,
                $state,
                $issue_rows,
                $file_rows
            );

            foreach ($issue_rows as $issue_index => $issue_row) {
                if (!is_array($issue_row)) {
                    continue;
                }
                $issue_rows[$issue_index]['recommended_actions'] = $this->aysa_adjustments_match_site_audit_issue_actions(
                    $issue_row,
                    $action_bundles
                );
            }
        }

        $page_issue_map = $this->aysa_adjustments_build_site_audit_page_issue_map($issue_rows);
        foreach ($page_rows as $page_index => $page_row) {
            if (!is_array($page_row)) {
                continue;
            }
            $page_url = esc_url_raw((string) ($page_row['url'] ?? ''));
            $page_issue_state = isset($page_issue_map[$page_url]) && is_array($page_issue_map[$page_url]) ? $page_issue_map[$page_url] : array();
            $page_rows[$page_index]['issues_count'] = intval($page_issue_state['issues_count'] ?? 0);
            $page_rows[$page_index]['issues_label'] = sanitize_text_field((string) ($page_issue_state['issues_label'] ?? '0 issues'));
            $page_rows[$page_index]['issue_priority'] = sanitize_text_field((string) ($page_issue_state['issue_priority'] ?? ''));
            $page_rows[$page_index]['top_issue_title'] = sanitize_text_field((string) ($page_issue_state['top_issue_title'] ?? ''));
            $page_rows[$page_index]['page_type'] = $this->aysa_adjustments_infer_site_audit_page_type($page_url);
        }

        $has_data = $valid_crawl && (!empty($audit) || !empty($file_rows));
        $conclusion = $this->aysa_adjustments_build_technical_site_audit_conclusion(
            $project_id,
            $state,
            $overview,
            $issue_rows,
            $page_rows,
            $file_rows,
            $statistics,
            $history_count,
            $action_bundles
        );

        return array(
            'id' => 'technical_site_audit',
            'status' => $snapshot_status,
            'valid_crawl' => $valid_crawl,
            'invalid_reason' => $valid_crawl ? '' : 'empty_or_missing_crawl_artifacts',
            'job_id' => sanitize_text_field((string) ($state['job_id'] ?? '')),
            'version_id' => intval($state['version_id'] ?? 0),
            'version_number' => intval($state['version_number'] ?? 0),
            'history_count' => $history_count,
            'target_url' => esc_url_raw((string) ($state['target_url'] ?? '')),
            'updated_at' => sanitize_text_field((string) ($state['updated_at'] ?? '')),
            'mode' => sanitize_text_field((string) ($state['mode'] ?? 'baseline_full')),
            'provider' => sanitize_text_field((string) ($state['provider'] ?? 'screamingfrog')),
            'pages_crawled' => $pages_crawled,
            'artifact_count' => count($file_rows),
            'csv_artifact_count' => $csv_file_count,
            'site_health' => intval($overview['site_health'] ?? 0),
            'errors' => intval($overview['errors'] ?? 0),
            'warnings' => intval($overview['warnings'] ?? 0),
            'count' => count($issue_rows),
            'overview_rows' => $overview_rows,
            'conclusion' => $conclusion,
            'action_bundles' => $action_bundles,
            'rows' => $issue_rows,
            'issue_groups' => $this->aysa_adjustments_build_site_audit_issue_groups($issue_rows),
            'severity_summary' => $this->aysa_adjustments_build_site_audit_severity_summary($issue_rows),
            'category_summary' => $this->aysa_adjustments_build_site_audit_category_summary($issue_rows),
            'pages' => $page_rows,
            'page_issue_map' => $page_issue_map,
            'files' => $file_rows,
            'exports' => $export_rows,
            'export_registry' => $export_registry_rows,
            'export_registry_total' => count($export_registry_rows),
            'export_rows_total' => array_sum(array_map(function($row) {
                return is_array($row) ? intval($row['row_count'] ?? 0) : 0;
            }, $export_registry_rows)),
            'has_data' => $has_data,
        );
    }

    private function aysa_adjustments_normalize_technical_site_audit_issue($row, $overview = array(), $issue_lifecycle = array()) {
        if (!is_array($row)) {
            return array();
        }

        $details = !empty($row['details']) && is_array($row['details']) ? $row['details'] : array();
        $sample_rows = !empty($details['sample_rows']) && is_array($details['sample_rows']) ? array_values($details['sample_rows']) : array();
        $sample_columns = !empty($details['sample_columns']) && is_array($details['sample_columns']) ? array_values($details['sample_columns']) : array();
        $examples = array();
        foreach (array_slice($sample_rows, 0, 5) as $sample_row) {
            if (!is_array($sample_row)) {
                continue;
            }
            $url = esc_url_raw((string) ($sample_row['url'] ?? $sample_row['Address'] ?? $sample_row['"Address"'] ?? $sample_row['﻿"Address"'] ?? ''));
            $title = sanitize_text_field((string) ($sample_row['title'] ?? $sample_row['Title'] ?? ''));
            if ($url === '' && $title === '') {
                continue;
            }
            $examples[] = array(
                'url' => $url,
                'title' => $title,
                'page_type' => $this->aysa_adjustments_infer_site_audit_page_type($url),
            );
        }

        $severity_raw = sanitize_text_field((string) ($row['severity'] ?? 'notice'));
        $title_raw = sanitize_text_field((string) ($row['title'] ?? $row['issue'] ?? ''));
        $description = sanitize_text_field((string) ($row['description'] ?? ''));
        $key = sanitize_key((string) ($row['key'] ?? ''));
        $affected_page_rows = !empty($row['affected_pages']) && is_array($row['affected_pages']) ? array_values($row['affected_pages']) : array();
        $affected_pages = intval($row['affected_pages_count'] ?? 0);
        if ($affected_pages <= 0 && empty($affected_page_rows)) {
            $affected_pages = intval($row['affected_pages'] ?? 0);
        } elseif ($affected_pages <= 0) {
            $affected_pages = count($affected_page_rows);
        }
        $category = $this->aysa_adjustments_detect_site_audit_issue_category($title_raw, $description, $key, $sample_columns);
        $priority = $this->aysa_adjustments_score_site_audit_issue_priority($category, $severity_raw, $title_raw, $description, $affected_pages, $examples, $overview);
        $page_type_hint = $this->aysa_adjustments_build_site_audit_issue_page_type_hint($examples);
        $issue_id = sanitize_key((string) ($row['issue_id'] ?? ''));
        if ($issue_id === '') {
            $issue_id = $this->aysa_technical_site_audit_issue_id($key, $title_raw, $category);
        }
        $lifecycle_row = isset($issue_lifecycle[$issue_id]) && is_array($issue_lifecycle[$issue_id]) ? $issue_lifecycle[$issue_id] : array();
        $lifecycle_state = sanitize_key((string) ($lifecycle_row['state'] ?? 'open'));
        if (!in_array($lifecycle_state, array('open', 'accepted', 'ignored', 'resolved', 'verified'), true)) {
            $lifecycle_state = 'open';
        }

        return array(
            'issue_id' => $issue_id,
            'audit_run_id' => sanitize_text_field((string) ($row['audit_run_id'] ?? '')),
            'project_id' => sanitize_text_field((string) ($row['project_id'] ?? '')),
            'source' => sanitize_text_field((string) ($row['source'] ?? 'screamingfrog')),
            'source_export' => sanitize_key((string) ($row['source_export'] ?? '')),
            'source_file' => sanitize_text_field((string) ($row['source_file'] ?? ($details['filename'] ?? ''))),
            'issue_type' => sanitize_key((string) ($row['issue_type'] ?? $key)),
            'lifecycle_state' => $lifecycle_state,
            'lifecycle_updated_at' => sanitize_text_field((string) ($lifecycle_row['updated_at'] ?? '')),
            'severity' => strtolower($severity_raw),
            'sf_severity' => sanitize_text_field((string) ($row['sf_severity'] ?? '')),
            'priority_score' => intval($row['priority_score'] ?? 0),
            'severity_raw' => $severity_raw,
            'priority_label' => $priority['label'],
            'priority_rank' => intval($priority['rank'] ?? 4),
            'title' => $title_raw,
            'title_plain' => $this->aysa_adjustments_build_site_audit_issue_title_plain($key, $category, $title_raw, $description, $examples),
            'impact_plain' => $this->aysa_adjustments_build_site_audit_issue_impact_plain($key, $category, $title_raw, $description, $examples),
            'first_action_plain' => $this->aysa_adjustments_build_site_audit_issue_action_plain($key, $category, $title_raw, $description, $examples),
            'affected_pages' => $affected_pages,
            'affected_pages_count' => $affected_pages,
            'affected_page_rows' => $this->aysa_adjustments_sanitize_site_audit_affected_rows($affected_page_rows),
            'affected_display' => $affected_pages > 0
                ? trim($affected_pages . ($page_type_hint !== '' ? ' pages · ' . $page_type_hint : ' pages'))
                : '0 pages',
            'category' => $category,
            'description' => $description,
            'root_cause' => sanitize_text_field((string) ($row['root_cause'] ?? '')),
            'explanation_plain' => sanitize_text_field((string) ($row['explanation_plain'] ?? '')),
            'explanation_technical' => sanitize_text_field((string) ($row['explanation_technical'] ?? '')),
            'impact' => sanitize_text_field((string) ($row['impact'] ?? '')),
            'recommended_action' => sanitize_text_field((string) ($row['recommended_action'] ?? '')),
            'action_type' => sanitize_key((string) ($row['action_type'] ?? '')),
            'auto_fix_possible' => !empty($row['auto_fix_possible']),
            'worker_action_key' => sanitize_key((string) ($row['worker_action_key'] ?? '')),
            'execution_target' => sanitize_key((string) ($row['execution_target'] ?? '')),
            'requires_approval' => !empty($row['requires_approval']),
            'verification_method' => sanitize_key((string) ($row['verification_method'] ?? '')),
            'key' => $key,
            'examples' => $examples,
            'details' => array(
                'filename' => sanitize_text_field((string) ($details['filename'] ?? '')),
                'sample_columns' => $sample_columns,
                'sample_rows' => $sample_rows,
                'full_rows_available' => !empty($details['full_rows_available']),
                'affected_rows_preserved' => intval($details['affected_rows_preserved'] ?? 0),
                'full_rows_count' => intval($details['full_rows_count'] ?? 0),
            ),
        );
    }

    private function aysa_adjustments_sanitize_site_audit_affected_rows($rows) {
        $rows = is_array($rows) ? array_values($rows) : array();
        $out = array();
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $evidence = !empty($row['evidence']) && is_array($row['evidence']) ? $row['evidence'] : array();
            $safe_evidence = array();
            foreach ($evidence as $key => $value) {
                $safe_evidence[sanitize_text_field((string) $key)] = sanitize_text_field((string) $value);
            }
            $out[] = array(
                'url' => esc_url_raw((string) ($row['url'] ?? '')),
                'page_title' => sanitize_text_field((string) ($row['page_title'] ?? '')),
                'status_code' => isset($row['status_code']) ? intval($row['status_code']) : null,
                'indexability' => sanitize_text_field((string) ($row['indexability'] ?? '')),
                'canonical_url' => esc_url_raw((string) ($row['canonical_url'] ?? '')),
                'crawl_depth' => sanitize_text_field((string) ($row['crawl_depth'] ?? '')),
                'inlinks' => sanitize_text_field((string) ($row['inlinks'] ?? '')),
                'outlinks' => sanitize_text_field((string) ($row['outlinks'] ?? '')),
                'evidence' => $safe_evidence,
            );
        }
        return $out;
    }

    private function aysa_adjustments_detect_site_audit_issue_category($title, $description, $key, $sample_columns = array()) {
        $haystack = strtolower(trim($title . ' ' . $description . ' ' . $key . ' ' . implode(' ', array_map('strval', $sample_columns))));

        if (strpos($haystack, '5xx') !== false || strpos($haystack, 'server error') !== false) {
            return 'server';
        }
        if (strpos($haystack, 'response code') !== false || strpos($haystack, 'http response') !== false || strpos($haystack, '4xx') !== false || strpos($haystack, 'client error') !== false || strpos($haystack, 'broken') !== false) {
            return 'response_codes';
        }
        if (strpos($haystack, 'meta description') !== false || strpos($haystack, 'page title') !== false || strpos($haystack, 'h1') !== false || strpos($haystack, 'h2') !== false) {
            return 'metadata';
        }
        if (strpos($haystack, 'canonical') !== false) {
            return 'canonical';
        }
        if (strpos($haystack, 'directive') !== false || strpos($haystack, 'noindex') !== false || strpos($haystack, 'non-indexable') !== false || strpos($haystack, 'indexable') !== false || strpos($haystack, 'robots') !== false) {
            return 'indexability';
        }
        if (strpos($haystack, 'inlink') !== false || strpos($haystack, 'internal link') !== false || strpos($haystack, 'orphan') !== false || strpos($haystack, 'depth') !== false || strpos($haystack, 'links') !== false) {
            return 'internal_linking';
        }
        if (strpos($haystack, 'image') !== false || strpos($haystack, 'alt') !== false) {
            return 'media';
        }
        if (strpos($haystack, 'structured data') !== false || strpos($haystack, 'javascript') !== false) {
            return 'technical';
        }

        return 'technical';
    }

    private function aysa_adjustments_infer_site_audit_page_type($url) {
        $url = trim((string) $url);
        if ($url === '') {
            return '';
        }
        $path = (string) wp_parse_url($url, PHP_URL_PATH);
        if ($path === '' || $path === '/') {
            return 'homepage';
        }
        if (preg_match('/\.(css|js|png|jpe?g|gif|svg|webp|pdf|xml)$/i', $path)) {
            return 'asset';
        }
        if (preg_match('#/(blog|articol|articole|news|noutati)/#i', $path)) {
            return 'content';
        }
        if (preg_match('#/(produs|product|shop|categoria|categorie|category|buchete|aranjamente|flori)/#i', $path)) {
            return 'commercial';
        }
        $segments = array_values(array_filter(explode('/', trim($path, '/'))));
        if (count($segments) <= 1) {
            return 'static';
        }
        return 'page';
    }

    private function aysa_adjustments_build_site_audit_issue_page_type_hint($examples) {
        if (!is_array($examples) || empty($examples)) {
            return '';
        }
        $counts = array();
        foreach ($examples as $example) {
            $type = sanitize_key((string) ($example['page_type'] ?? ''));
            if ($type === '' || $type === 'page') {
                continue;
            }
            if (!isset($counts[$type])) {
                $counts[$type] = 0;
            }
            $counts[$type]++;
        }
        if (empty($counts)) {
            return '';
        }
        arsort($counts);
        $label = (string) key($counts);
        $map = array(
            'homepage' => 'homepage-heavy',
            'commercial' => 'mostly commercial URLs',
            'content' => 'mostly content URLs',
            'static' => 'mostly static URLs',
            'asset' => 'mostly technical assets',
        );
        return $map[$label] ?? ('mostly ' . $label . ' URLs');
    }

    private function aysa_adjustments_score_site_audit_issue_priority($category, $severity_raw, $title_raw, $description, $affected_pages, $examples, $overview = array()) {
        $severity = strtolower((string) $severity_raw);
        $haystack = strtolower(trim($title_raw . ' ' . $description));
        $rank = 4;

        if ($category === 'server' || strpos($haystack, '5xx') !== false) {
            $rank = 1;
        } elseif ($category === 'indexability' || $category === 'canonical') {
            $rank = 1;
        } elseif ($category === 'response_codes' && (strpos($haystack, '4xx') !== false || strpos($haystack, 'client error') !== false)) {
            $rank = 1;
        } elseif ($category === 'metadata') {
            $rank = 2;
        } elseif ($category === 'internal_linking') {
            $rank = 2;
        } elseif ($category === 'media') {
            $rank = 3;
        } elseif ($severity === 'error') {
            $rank = 2;
        } elseif ($severity === 'warning') {
            $rank = 3;
        }

        if ($this->aysa_adjustments_examples_include_commercial_urls($examples) && $rank > 1) {
            $rank--;
        }
        if ($this->aysa_adjustments_examples_are_assets_only($examples) && $rank < 4) {
            $rank++;
        }
        if ($affected_pages >= 100 && $rank > 1) {
            $rank--;
        }
        $rank = max(1, min(4, $rank));

        $labels = array(
            1 => 'Fix now',
            2 => 'High',
            3 => 'Medium',
            4 => 'Low',
        );
        return array(
            'rank' => $rank,
            'label' => $labels[$rank] ?? 'Low',
        );
    }

    private function aysa_adjustments_examples_include_commercial_urls($examples) {
        if (!is_array($examples)) {
            return false;
        }
        foreach ($examples as $example) {
            if (($example['page_type'] ?? '') === 'commercial' || ($example['page_type'] ?? '') === 'homepage') {
                return true;
            }
        }
        return false;
    }

    private function aysa_adjustments_examples_are_assets_only($examples) {
        if (!is_array($examples) || empty($examples)) {
            return false;
        }
        foreach ($examples as $example) {
            if (($example['page_type'] ?? '') !== 'asset') {
                return false;
            }
        }
        return true;
    }

    private function aysa_adjustments_site_audit_issue_copy_by_key($key) {
        $map = array(
            'client_error_inlinks' => array(
                'title' => 'Broken internal links point to 4xx URLs',
                'impact' => 'Users and crawlers hit broken destinations from internal links.',
                'action' => 'Fix the broken targets or remove the internal links pointing to them.',
            ),
            'response_codes_4xx' => array(
                'title' => 'Internal URLs return 4xx status codes',
                'impact' => 'Broken internal URLs waste crawl budget and create dead ends.',
                'action' => 'Review the broken URLs and repair the destination or the internal link source.',
            ),
            'response_codes_5xx' => array(
                'title' => 'Internal URLs return 5xx status codes',
                'impact' => 'Server failures block both Google and users from loading pages.',
                'action' => 'Fix the server or application errors before SEO cleanup.',
            ),
            'meta_description_missing' => array(
                'title' => 'Meta descriptions are missing',
                'impact' => 'Missing snippets can reduce click-through rate in search results.',
                'action' => 'Start with homepage and commercial templates, then fill the remaining important URLs.',
            ),
            'meta_description_too_long' => array(
                'title' => 'Meta descriptions are too long',
                'impact' => 'Long snippets can truncate and make search previews less useful.',
                'action' => 'Shorten the template pattern first, then clean up key pages.',
            ),
            'page_title_too_long' => array(
                'title' => 'Page titles are too long',
                'impact' => 'Overlong titles truncate in Google and usually signal template bloat.',
                'action' => 'Reduce the title pattern in shared templates before editing URLs one by one.',
            ),
            'page_title_duplicates' => array(
                'title' => 'Page titles are duplicated',
                'impact' => 'Duplicate titles weaken uniqueness and relevance signals across URLs.',
                'action' => 'Find the repeated title pattern and make titles unique for key pages.',
            ),
            'h1_missing' => array(
                'title' => 'Pages are missing an H1 heading',
                'impact' => 'Missing H1 headings weaken topical clarity for search engines and users.',
                'action' => 'Add one clear H1 on the affected page templates or archives.',
            ),
            'h1_multiple' => array(
                'title' => 'Pages have multiple H1 headings',
                'impact' => 'Multiple H1s usually point to template duplication or conflicting markup.',
                'action' => 'Keep a single primary H1 per page and remove duplicate heading output.',
            ),
            'h1_duplicate' => array(
                'title' => 'Pages repeat the same H1 twice',
                'impact' => 'Duplicate H1 output is a template-level markup problem.',
                'action' => 'Inspect the template and remove the repeated H1 block.',
            ),
            'h2_multiple' => array(
                'title' => 'Pages have many H2 headings',
                'impact' => 'Dense H2 output can indicate noisy templates or weak content structure.',
                'action' => 'Review the affected templates and keep only meaningful section headings.',
            ),
            'h2_too_long' => array(
                'title' => 'H2 headings are too long',
                'impact' => 'Very long H2 headings make content structure harder to scan.',
                'action' => 'Shorten repeated H2 patterns and clean up verbose section headings.',
            ),
            'canonicalised_inlinks' => array(
                'title' => 'Internal links point to non-canonical URLs',
                'impact' => 'Internal links reinforce URLs that are not the preferred canonical destination.',
                'action' => 'Update internal links to point directly to the canonical URLs.',
            ),
            'canonical_chains' => array(
                'title' => 'Canonical chains were found',
                'impact' => 'Canonical chains can confuse search engines about the preferred URL.',
                'action' => 'Flatten the chain so every page points directly to the final canonical URL.',
            ),
            'canonicals_missing' => array(
                'title' => 'Canonical tags are missing on indexable pages',
                'impact' => 'Missing canonicals make preferred URL selection less explicit.',
                'action' => 'Add self-referencing canonicals to the affected important pages.',
            ),
            'noindex_inlinks' => array(
                'title' => 'Noindex URLs still receive internal links',
                'impact' => 'Internal links keep pushing crawlers toward pages that should not index.',
                'action' => 'Remove or reduce links to noindex pages unless there is a product reason to keep them.',
            ),
            'directives_noindex' => array(
                'title' => 'URLs carry noindex directives',
                'impact' => 'Noindex rules can hide pages from Google if applied too broadly.',
                'action' => 'Verify whether each noindex URL is intentionally excluded from search.',
            ),
            'missing_inlinks_rows' => array(
                'title' => 'Many URLs have weak internal linking',
                'impact' => 'Weak inlinks make important pages harder to discover and support with authority.',
                'action' => 'Prioritize key commercial and static pages for stronger navigation and contextual links.',
            ),
            'orphan_urls' => array(
                'title' => 'Potential orphan URLs were found',
                'impact' => 'Orphan URLs are harder for users and crawlers to discover naturally.',
                'action' => 'Reconnect the valid orphan pages into categories, menus, or contextual links.',
            ),
            'redirect_chains_found' => array(
                'title' => 'Redirect chains were found',
                'impact' => 'Redirect chains slow down crawling and dilute signals across hops.',
                'action' => 'Replace chained redirects with one direct redirect to the final URL.',
            ),
            'images_over_size' => array(
                'title' => 'Images are too heavy',
                'impact' => 'Large images can slow important pages and affect UX.',
                'action' => 'Compress or resize the heavy images first on important pages.',
            ),
            'images_missing_alt' => array(
                'title' => 'Images are missing alt text',
                'impact' => 'Missing alt text weakens accessibility and image-search relevance.',
                'action' => 'Add descriptive alt text to the affected important images.',
            ),
        );
        $key = sanitize_key((string) $key);
        return isset($map[$key]) && is_array($map[$key]) ? $map[$key] : array();
    }

    private function aysa_adjustments_build_site_audit_issue_title_plain($key, $category, $title_raw, $description, $examples) {
        $copy = $this->aysa_adjustments_site_audit_issue_copy_by_key($key);
        if (!empty($copy['title'])) {
            return sanitize_text_field((string) $copy['title']);
        }
        $haystack = strtolower(trim($title_raw . ' ' . $description));

        if ($category === 'server' || strpos($haystack, '5xx') !== false) {
            return 'Server errors are blocking important pages';
        }
        if ($category === 'response_codes' && (strpos($haystack, 'response code') !== false || strpos($haystack, 'http response') !== false) && strpos($haystack, '4xx') === false) {
            return 'Response-code dataset needs review across crawled URLs';
        }
        if ($category === 'response_codes' && (strpos($haystack, '4xx') !== false || strpos($haystack, 'client error') !== false)) {
            return 'Broken internal URLs are sending users and crawlers to 4xx pages';
        }
        if ($category === 'metadata' && strpos($haystack, 'meta description') !== false) {
            return 'Meta description dataset needs review across crawled URLs';
        }
        if ($category === 'metadata' && strpos($haystack, 'title') !== false) {
            return 'Title tag dataset needs review across crawled URLs';
        }
        if ($category === 'metadata' && (strpos($haystack, 'h1') !== false || strpos($haystack, 'h2') !== false)) {
            return 'Heading dataset needs review across crawled URLs';
        }
        if ($category === 'canonical') {
            return 'Canonical dataset needs review across crawled URLs';
        }
        if ($category === 'indexability') {
            return 'Indexability dataset needs review across crawled URLs';
        }
        if ($category === 'internal_linking' && strpos($haystack, 'depth') !== false) {
            return 'Important pages are buried too deep in the site structure';
        }
        if ($category === 'internal_linking') {
            return 'Internal linking is too weak for some pages';
        }
        if ($category === 'media') {
            return 'Image optimization signals are incomplete';
        }

        return $title_raw !== '' ? $title_raw : 'Technical issue requires review';
    }

    private function aysa_adjustments_build_site_audit_issue_impact_plain($key, $category, $title_raw, $description, $examples) {
        $copy = $this->aysa_adjustments_site_audit_issue_copy_by_key($key);
        if (!empty($copy['impact'])) {
            return sanitize_text_field((string) $copy['impact']);
        }
        $haystack = strtolower(trim($title_raw . ' ' . $description));

        if ($category === 'server') {
            return 'Google and users may fail to load these pages, which can suppress visibility and conversions immediately.';
        }
        if ($category === 'response_codes' && (strpos($haystack, 'response code') !== false || strpos($haystack, 'http response') !== false) && strpos($haystack, '4xx') === false) {
            return 'This export family needs review because redirects, client errors, or header-level response issues can waste crawl budget and weaken site quality signals.';
        }
        if ($category === 'response_codes' && (strpos($haystack, '4xx') !== false || strpos($haystack, 'client error') !== false)) {
            return 'Broken internal targets waste crawl budget and create dead ends for users.';
        }
        if ($category === 'metadata' && strpos($haystack, 'meta description') !== false) {
            return 'The saved export needs review because weak or missing snippets can reduce click-through rate from Google even when pages rank.';
        }
        if ($category === 'metadata') {
            return 'This export family needs review because shared title, heading, or snippet patterns may be repeated across many URLs.';
        }
        if ($category === 'canonical' || $category === 'indexability') {
            return 'This export family needs review because search engines may crawl the wrong URLs or ignore pages that should be ranking.';
        }
        if ($category === 'internal_linking') {
            return 'Weak internal links make key pages harder to discover and harder to support with authority.';
        }
        if ($category === 'media') {
            return 'Missing image signals reduce accessibility and can limit image search visibility.';
        }

        return 'This can limit crawl quality or search visibility.';
    }

    private function aysa_adjustments_build_site_audit_issue_action_plain($key, $category, $title_raw, $description, $examples) {
        $copy = $this->aysa_adjustments_site_audit_issue_copy_by_key($key);
        if (!empty($copy['action'])) {
            return sanitize_text_field((string) $copy['action']);
        }
        $haystack = strtolower(trim($title_raw . ' ' . $description));

        if ($category === 'server') {
            return 'Check the affected URLs first and fix the server/application failures before any SEO cleanup.';
        }
        if ($category === 'response_codes' && (strpos($haystack, 'response code') !== false || strpos($haystack, 'http response') !== false) && strpos($haystack, '4xx') === false) {
            return 'Review the affected URLs for redirects, unexpected response codes, and template-generated technical noise.';
        }
        if ($category === 'response_codes' && (strpos($haystack, '4xx') !== false || strpos($haystack, 'client error') !== false)) {
            return 'Repair the broken destinations or remove the internal links that point to them.';
        }
        if ($category === 'metadata' && strpos($haystack, 'meta description') !== false) {
            return 'Open the saved URLs first, confirm the missing or weak snippets, then fix the shared meta template for the highest-value pages.';
        }
        if ($category === 'metadata') {
            return 'Review the saved URLs and template pattern first, then decide what should be fixed at template level versus page level.';
        }
        if ($category === 'canonical' || $category === 'indexability') {
            return 'Audit the canonical and indexing rules on the affected templates before content work.';
        }
        if ($category === 'internal_linking' && strpos($haystack, 'depth') !== false) {
            return 'Reduce click depth for important pages through navigation, category links, and contextual links.';
        }
        if ($category === 'internal_linking') {
            return 'Add stronger internal links from high-authority pages to the affected URLs.';
        }
        if ($category === 'media') {
            return 'Update image templates and review the affected image URLs first.';
        }

        return 'Open details and review the affected URLs first.';
    }

    private function aysa_adjustments_build_site_audit_issue_groups($issue_rows) {
        $issue_rows = is_array($issue_rows) ? array_values($issue_rows) : array();
        $groups = array(
            'error' => array('severity' => 'error', 'label' => 'Errors', 'issues' => array()),
            'warning' => array('severity' => 'warning', 'label' => 'Warnings', 'issues' => array()),
            'notice' => array('severity' => 'notice', 'label' => 'Notices', 'issues' => array()),
        );

        foreach ($issue_rows as $issue_row) {
            if (!is_array($issue_row)) {
                continue;
            }
            $severity = sanitize_key((string) ($issue_row['severity'] ?? 'notice'));
            if (!isset($groups[$severity])) {
                $severity = 'notice';
            }
            $groups[$severity]['issues'][] = $issue_row;
        }

        $out = array();
        foreach (array('error', 'warning', 'notice') as $severity_key) {
            $group = $groups[$severity_key];
            $group['count'] = count($group['issues']);
            $out[] = $group;
        }

        return $out;
    }

    private function aysa_adjustments_build_site_audit_severity_summary($issue_rows) {
        $issue_rows = is_array($issue_rows) ? array_values($issue_rows) : array();
        $summary = array(
            'all' => 0,
            'error' => 0,
            'warning' => 0,
            'notice' => 0,
        );
        foreach ($issue_rows as $issue_row) {
            if (!is_array($issue_row)) {
                continue;
            }
            $summary['all']++;
            $severity = sanitize_key((string) ($issue_row['severity'] ?? 'notice'));
            if (!isset($summary[$severity])) {
                $severity = 'notice';
            }
            $summary[$severity]++;
        }
        return $summary;
    }

    private function aysa_adjustments_build_site_audit_category_summary($issue_rows) {
        $issue_rows = is_array($issue_rows) ? array_values($issue_rows) : array();
        $label_map = array(
            'response_codes' => 'Crawlability',
            'server' => 'Crawlability',
            'canonical' => 'Canonicalization',
            'indexability' => 'Indexability',
            'metadata' => 'Meta Tags',
            'internal_linking' => 'Internal Linking',
            'media' => 'Images',
            'technical' => 'Technical',
        );

        $counts = array();
        foreach ($issue_rows as $issue_row) {
            if (!is_array($issue_row)) {
                continue;
            }
            $category = sanitize_key((string) ($issue_row['category'] ?? 'technical'));
            if ($category === '') {
                $category = 'technical';
            }
            if (!isset($counts[$category])) {
                $counts[$category] = 0;
            }
            $counts[$category]++;
        }

        arsort($counts);
        $out = array();
        foreach ($counts as $key => $count) {
            $out[] = array(
                'key' => $key,
                'label' => sanitize_text_field((string) ($label_map[$key] ?? ucwords(str_replace('_', ' ', $key)))),
                'count' => intval($count),
            );
        }

        return $out;
    }

    private function aysa_adjustments_build_site_audit_page_issue_map($issue_rows) {
        $issue_rows = is_array($issue_rows) ? array_values($issue_rows) : array();
        $map = array();

        foreach ($issue_rows as $issue_row) {
            if (!is_array($issue_row)) {
                continue;
            }
            $priority_rank = intval($issue_row['priority_rank'] ?? 99);
            $priority_label = sanitize_text_field((string) ($issue_row['priority_label'] ?? ''));
            $title_plain = sanitize_text_field((string) ($issue_row['title_plain'] ?? $issue_row['title'] ?? ''));
            $examples = !empty($issue_row['details']['sample_rows']) && is_array($issue_row['details']['sample_rows'])
                ? array_values($issue_row['details']['sample_rows'])
                : (is_array($issue_row['examples'] ?? null) ? array_values($issue_row['examples']) : array());

            foreach ($examples as $example) {
                if (!is_array($example)) {
                    continue;
                }
                $url = esc_url_raw((string) ($example['url'] ?? $example['Address'] ?? $example['"Address"'] ?? $example['﻿"Address"'] ?? ''));
                if ($url === '') {
                    continue;
                }
                if (!isset($map[$url])) {
                    $map[$url] = array(
                        'issues_count' => 0,
                        'issue_priority' => '',
                        'top_issue_title' => '',
                        'priority_rank' => 99,
                    );
                }
                $map[$url]['issues_count']++;
                if ($priority_rank < intval($map[$url]['priority_rank'])) {
                    $map[$url]['priority_rank'] = $priority_rank;
                    $map[$url]['issue_priority'] = $priority_label;
                    $map[$url]['top_issue_title'] = $title_plain;
                }
            }
        }

        foreach ($map as $url => $row) {
            $count = intval($row['issues_count'] ?? 0);
            $row['issues_label'] = $count === 1 ? '1 issue' : ($count . ' issues');
            unset($row['priority_rank']);
            $map[$url] = $row;
        }

        return $map;
    }

    private function aysa_adjustments_classify_site_audit_issue_subtype($issue_row) {
        if (!is_array($issue_row)) {
            return '';
        }

        $category = sanitize_key((string) ($issue_row['category'] ?? 'technical'));
        $haystack = strtolower(trim(
            sanitize_text_field((string) ($issue_row['title'] ?? '')) . ' ' .
            sanitize_text_field((string) ($issue_row['title_plain'] ?? '')) . ' ' .
            sanitize_text_field((string) ($issue_row['description'] ?? ''))
        ));

        if ($category === 'metadata') {
            if (strpos($haystack, 'meta description') !== false) {
                return 'missing_or_weak_meta_descriptions';
            }
            if (strpos($haystack, 'page title') !== false || strpos($haystack, 'title') !== false) {
                return 'title_tag_issues';
            }
            if (strpos($haystack, 'h1') !== false) {
                return 'h1_issues';
            }
            if (strpos($haystack, 'h2') !== false) {
                return 'h2_issues';
            }
            return 'metadata_issues';
        }

        if ($category === 'response_codes' || $category === 'server') {
            if (strpos($haystack, '5xx') !== false || strpos($haystack, 'server error') !== false) {
                return 'server_errors';
            }
            if (strpos($haystack, '4xx') !== false || strpos($haystack, 'client error') !== false || strpos($haystack, 'broken') !== false) {
                return 'broken_internal_urls';
            }
            if (strpos($haystack, 'redirect') !== false) {
                return 'redirect_issues';
            }
            return 'response_code_issues';
        }

        if ($category === 'canonical') {
            return 'canonical_issues';
        }
        if ($category === 'indexability') {
            return 'indexability_issues';
        }
        if ($category === 'internal_linking') {
            if (strpos($haystack, 'depth') !== false) {
                return 'crawl_depth_issues';
            }
            if (strpos($haystack, 'orphan') !== false) {
                return 'orphan_or_missing_inlinks';
            }
            return 'internal_linking_issues';
        }

        return 'technical_issues';
    }

    private function aysa_adjustments_site_audit_issue_subtype_label($subtype) {
        $labels = array(
            'missing_or_weak_meta_descriptions' => 'Meta description review dataset',
            'title_tag_issues' => 'Title tag review dataset',
            'h1_issues' => 'H1 review dataset',
            'h2_issues' => 'H2 review dataset',
            'metadata_issues' => 'Metadata review dataset',
            'server_errors' => 'Server error review dataset',
            'broken_internal_urls' => 'Broken URL review dataset',
            'redirect_issues' => 'Redirect review dataset',
            'response_code_issues' => 'Response-code review dataset',
            'canonical_issues' => 'Canonical review dataset',
            'indexability_issues' => 'Indexability review dataset',
            'crawl_depth_issues' => 'Crawl-depth review dataset',
            'orphan_or_missing_inlinks' => 'Weak-link review dataset',
            'internal_linking_issues' => 'Internal-linking review dataset',
            'technical_issues' => 'Technical review dataset',
        );
        $subtype = sanitize_key((string) $subtype);
        return sanitize_text_field((string) ($labels[$subtype] ?? ucwords(str_replace('_', ' ', $subtype))));
    }

    private function aysa_adjustments_build_site_audit_bundle_breakdown($related_issues) {
        $related_issues = is_array($related_issues) ? array_values($related_issues) : array();
        $counts = array();
        foreach ($related_issues as $issue_row) {
            if (!is_array($issue_row)) {
                continue;
            }
            $subtype = $this->aysa_adjustments_classify_site_audit_issue_subtype($issue_row);
            if ($subtype === '') {
                continue;
            }
            $count = intval($issue_row['affected_pages'] ?? 0);
            if (!isset($counts[$subtype]) || $count > intval($counts[$subtype]['affected_count'] ?? 0)) {
                $counts[$subtype] = array(
                    'key' => $subtype,
                    'label' => $this->aysa_adjustments_site_audit_issue_subtype_label($subtype),
                    'affected_count' => $count,
                    'title' => sanitize_text_field((string) ($issue_row['title_plain'] ?? $issue_row['title'] ?? '')),
                );
            }
        }

        usort($counts, function($left, $right) {
            $left_count = intval($left['affected_count'] ?? 0);
            $right_count = intval($right['affected_count'] ?? 0);
            if ($left_count === $right_count) {
                return 0;
            }
            return ($left_count > $right_count) ? -1 : 1;
        });

        return array_values(array_slice($counts, 0, 4));
    }

    private function aysa_adjustments_site_audit_action_bundle_catalog() {
        return array(
            'prepare_metadata_remediation' => array(
                'action_key' => 'prepare_metadata_remediation',
                'action_label' => 'Prepare metadata remediation',
                'bundle_type' => 'metadata',
                'categories' => array('metadata'),
                'source_files' => array('page_titles_all.csv', 'meta_description_all.csv', 'h1_all.csv', 'h2_all.csv'),
                'target_scope' => 'template_and_url_review',
                'next_step' => 'Review the affected templates and prepare the remediation shortlist for key URLs first.',
                'default_reason' => 'Page title, meta description, and heading issues are weakening relevance and click-through signals.',
            ),
            'prepare_response_code_review' => array(
                'action_key' => 'prepare_response_code_review',
                'action_label' => 'Prepare response-code review',
                'bundle_type' => 'response_codes',
                'categories' => array('server', 'response_codes'),
                'source_files' => array('response_codes_all.csv', 'redirect_chains.csv', 'redirects.csv', 'internal_client_error_(4xx)_inlinks.csv', 'all_http_response_headers.csv'),
                'target_scope' => 'crawl_cleanup_and_redirect_review',
                'next_step' => 'Validate the affected URLs, redirect patterns, and broken internal targets before the next crawl.',
                'default_reason' => 'Response-code issues can waste crawl budget and send users or crawlers to broken destinations.',
            ),
            'prepare_indexability_review' => array(
                'action_key' => 'prepare_indexability_review',
                'action_label' => 'Prepare indexability review',
                'bundle_type' => 'indexability',
                'categories' => array('canonical', 'indexability'),
                'source_files' => array('canonicals_all.csv', 'directives_all.csv', 'noindex_inlinks.csv', 'canonicalised_inlinks.csv'),
                'target_scope' => 'canonical_and_indexing_rules',
                'next_step' => 'Review canonical, noindex, and directive conflicts before changing content or linking.',
                'default_reason' => 'Canonical and indexing conflicts can push Google toward the wrong URLs.',
            ),
            'prepare_internal_linking_plan' => array(
                'action_key' => 'prepare_internal_linking_plan',
                'action_label' => 'Prepare internal-linking plan',
                'bundle_type' => 'internal_linking',
                'categories' => array('internal_linking'),
                'source_files' => array('all_inlinks.csv', 'all_outlinks.csv', 'missing_inlinks.csv', 'orphan_urls_inlinks.csv'),
                'target_scope' => 'linking_and_architecture_plan',
                'next_step' => 'Prepare the internal-linking shortlist for weak pages and navigation-level fixes.',
                'default_reason' => 'Weak internal linking makes important pages harder to discover and support with authority.',
            ),
            'prepare_image_alt_text' => array(
                'action_key' => 'prepare_image_alt_text',
                'action_label' => 'Prepare image alt-text remediation',
                'bundle_type' => 'media',
                'categories' => array('media'),
                'source_files' => array('images_missing_alt_text_inlinks.csv', 'images_over_x_kb_inlinks.csv'),
                'target_scope' => 'image_alt_text_and_media_review',
                'next_step' => 'Prepare image alt text and media cleanup candidates for affected image rows.',
                'default_reason' => 'Missing alt text and heavy image rows can weaken accessibility, image relevance, and performance.',
            ),
        );
    }

    private function aysa_adjustments_site_audit_bundle_category_to_action_key($category) {
        $category = sanitize_key((string) $category);
        return match ($category) {
            'metadata' => 'prepare_metadata_remediation',
            'server', 'response_codes' => 'prepare_response_code_review',
            'canonical', 'indexability' => 'prepare_indexability_review',
            'internal_linking' => 'prepare_internal_linking_plan',
            'media' => 'prepare_image_alt_text',
            default => '',
        };
    }

    private function aysa_adjustments_get_site_audit_action_bundle_state_map($state) {
        $state = is_array($state) ? $state : array();
        $bundles = isset($state['action_bundles']) && is_array($state['action_bundles']) ? $state['action_bundles'] : array();
        $normalized = array();
        foreach ($bundles as $action_key => $row) {
            $normalized_key = sanitize_key((string) $action_key);
            if ($normalized_key === '' || !is_array($row)) {
                continue;
            }
            $normalized[$normalized_key] = array(
                'approval_state' => sanitize_key((string) ($row['approval_state'] ?? 'suggested')),
                'execution_state' => sanitize_key((string) ($row['execution_state'] ?? 'open')),
                'execution_package_id' => sanitize_text_field((string) ($row['execution_package_id'] ?? '')),
                'metadata_execution_package' => is_array($row['metadata_execution_package'] ?? null) ? $row['metadata_execution_package'] : array(),
                'canonical_execution_package' => is_array($row['canonical_execution_package'] ?? null) ? $row['canonical_execution_package'] : array(),
                'image_alt_execution_package' => is_array($row['image_alt_execution_package'] ?? null) ? $row['image_alt_execution_package'] : array(),
                'last_prepare_result' => is_array($row['last_prepare_result'] ?? null) ? $row['last_prepare_result'] : array(),
                'last_apply_result' => is_array($row['last_apply_result'] ?? null) ? $row['last_apply_result'] : array(),
                'last_verify_result' => is_array($row['last_verify_result'] ?? null) ? $row['last_verify_result'] : array(),
                'last_error' => sanitize_text_field((string) ($row['last_error'] ?? '')),
                'last_prepared_summary' => sanitize_text_field((string) ($row['last_prepared_summary'] ?? '')),
                'last_prepared_at' => sanitize_text_field((string) ($row['last_prepared_at'] ?? '')),
                'last_applied_at' => sanitize_text_field((string) ($row['last_applied_at'] ?? '')),
                'last_verified_at' => sanitize_text_field((string) ($row['last_verified_at'] ?? '')),
                'approved_at' => sanitize_text_field((string) ($row['approved_at'] ?? '')),
                'dismissed_at' => sanitize_text_field((string) ($row['dismissed_at'] ?? '')),
                'last_sent_to_agent8_at' => sanitize_text_field((string) ($row['last_sent_to_agent8_at'] ?? '')),
            );
        }
        return $normalized;
    }

    private function aysa_adjustments_build_technical_site_audit_action_bundles($project_id, $state, $issues, $files) {
        $catalog = $this->aysa_adjustments_site_audit_action_bundle_catalog();
        $persisted_state = $this->aysa_adjustments_get_site_audit_action_bundle_state_map($state);
        $issues = is_array($issues) ? array_values($issues) : array();
        $files = is_array($files) ? array_values($files) : array();
        $bundles = array();

        foreach ($catalog as $action_key => $config) {
            $categories = isset($config['categories']) && is_array($config['categories']) ? array_values($config['categories']) : array();
            $related_issues = array_values(array_filter($issues, function($issue) use ($categories) {
                if (!is_array($issue)) {
                    return false;
                }
                return in_array(sanitize_key((string) ($issue['category'] ?? '')), $categories, true);
            }));
            if (empty($related_issues)) {
                continue;
            }

            usort($related_issues, function($left, $right) {
                $left_rank = intval($left['priority_rank'] ?? 99);
                $right_rank = intval($right['priority_rank'] ?? 99);
                if ($left_rank !== $right_rank) {
                    return ($left_rank < $right_rank) ? -1 : 1;
                }
                $left_count = intval($left['affected_pages'] ?? 0);
                $right_count = intval($right['affected_pages'] ?? 0);
                if ($left_count === $right_count) {
                    return 0;
                }
                return ($left_count > $right_count) ? -1 : 1;
            });

            $top_issue = $related_issues[0];
            $issue_breakdown = $this->aysa_adjustments_build_site_audit_bundle_breakdown($related_issues);
            $preview_urls = array();
            foreach ($related_issues as $issue) {
                foreach ((array) ($issue['examples'] ?? array()) as $example) {
                    $url = esc_url_raw((string) ($example['url'] ?? ''));
                    if ($url === '' || isset($preview_urls[$url])) {
                        continue;
                    }
                    $preview_urls[$url] = $url;
                    if (count($preview_urls) >= 5) {
                        break 2;
                    }
                }
            }

            $matched_source_files = array();
            foreach ((array) ($config['source_files'] ?? array()) as $expected_filename) {
                foreach ($files as $file_row) {
                    if (!is_array($file_row)) {
                        continue;
                    }
                    if (sanitize_text_field((string) ($file_row['filename'] ?? '')) === $expected_filename) {
                        $matched_source_files[] = $expected_filename;
                        break;
                    }
                }
            }

            $persisted = isset($persisted_state[$action_key]) && is_array($persisted_state[$action_key]) ? $persisted_state[$action_key] : array();
            $approval_state = sanitize_key((string) ($persisted['approval_state'] ?? 'suggested'));
            if (!in_array($approval_state, array('suggested', 'approved', 'preparing', 'prepared', 'applying', 'applied', 'failed', 'manual_needed', 'verifying', 'verification_pending', 'verified', 'dismissed'), true)) {
                $approval_state = 'suggested';
            }

            $bundles[] = array(
                'action_key' => sanitize_key((string) ($config['action_key'] ?? $action_key)),
                'action_label' => sanitize_text_field((string) ($config['action_label'] ?? $action_key)),
                'bundle_type' => sanitize_key((string) ($config['bundle_type'] ?? 'technical')),
                'priority_label' => sanitize_text_field((string) ($top_issue['priority_label'] ?? 'Medium')),
                'priority_rank' => intval($top_issue['priority_rank'] ?? 4),
                'reason' => sanitize_text_field((string) ($top_issue['impact_plain'] ?? $config['default_reason'] ?? 'This audit family needs preparation before execution.')),
                'issue_category' => sanitize_key((string) ($top_issue['category'] ?? 'technical')),
                'issue_title' => sanitize_text_field((string) ($top_issue['title_plain'] ?? $top_issue['title'] ?? 'Technical remediation bundle')),
                'target_scope' => sanitize_key((string) ($config['target_scope'] ?? 'review')),
                'affected_count' => max(array_map(static function($issue) {
                    return intval($issue['affected_pages'] ?? 0);
                }, $related_issues)),
                'affected_urls_preview' => array_values($preview_urls),
                'source_files' => array_values($matched_source_files),
                'next_step' => sanitize_text_field((string) ($config['next_step'] ?? 'Review the affected URLs and prepare the remediation plan.')),
                'issue_breakdown' => $issue_breakdown,
                'requires_approval' => true,
                'approval_state' => $approval_state,
                'execution_state' => sanitize_key((string) ($persisted['execution_state'] ?? 'open')),
                'execution_package_id' => sanitize_text_field((string) ($persisted['execution_package_id'] ?? '')),
                'metadata_execution_package' => is_array($persisted['metadata_execution_package'] ?? null) ? $persisted['metadata_execution_package'] : array(),
                'canonical_execution_package' => is_array($persisted['canonical_execution_package'] ?? null) ? $persisted['canonical_execution_package'] : array(),
                'image_alt_execution_package' => is_array($persisted['image_alt_execution_package'] ?? null) ? $persisted['image_alt_execution_package'] : array(),
                'last_prepare_result' => is_array($persisted['last_prepare_result'] ?? null) ? $persisted['last_prepare_result'] : array(),
                'last_apply_result' => is_array($persisted['last_apply_result'] ?? null) ? $persisted['last_apply_result'] : array(),
                'last_verify_result' => is_array($persisted['last_verify_result'] ?? null) ? $persisted['last_verify_result'] : array(),
                'last_error' => sanitize_text_field((string) ($persisted['last_error'] ?? '')),
                'last_prepared_summary' => sanitize_text_field((string) ($persisted['last_prepared_summary'] ?? '')),
                'last_prepared_at' => sanitize_text_field((string) ($persisted['last_prepared_at'] ?? '')),
                'last_applied_at' => sanitize_text_field((string) ($persisted['last_applied_at'] ?? '')),
                'last_verified_at' => sanitize_text_field((string) ($persisted['last_verified_at'] ?? '')),
                'approved_at' => sanitize_text_field((string) ($persisted['approved_at'] ?? '')),
                'dismissed_at' => sanitize_text_field((string) ($persisted['dismissed_at'] ?? '')),
                'last_sent_to_agent8_at' => sanitize_text_field((string) ($persisted['last_sent_to_agent8_at'] ?? '')),
            );
        }

        usort($bundles, function($left, $right) {
            $left_rank = intval($left['priority_rank'] ?? 99);
            $right_rank = intval($right['priority_rank'] ?? 99);
            if ($left_rank !== $right_rank) {
                return ($left_rank < $right_rank) ? -1 : 1;
            }
            $left_count = intval($left['affected_count'] ?? 0);
            $right_count = intval($right['affected_count'] ?? 0);
            if ($left_count === $right_count) {
                return 0;
            }
            return ($left_count > $right_count) ? -1 : 1;
        });

        return array_values($bundles);
    }

    private function aysa_adjustments_match_site_audit_issue_actions($issue_row, $action_bundles) {
        if (!is_array($issue_row) || !is_array($action_bundles)) {
            return array();
        }
        $action_key = $this->aysa_adjustments_site_audit_bundle_category_to_action_key((string) ($issue_row['category'] ?? ''));
        if ($action_key === '') {
            return array();
        }
        foreach ($action_bundles as $bundle) {
            if (!is_array($bundle)) {
                continue;
            }
            if (sanitize_key((string) ($bundle['action_key'] ?? '')) !== $action_key) {
                continue;
            }
            return array(
                array(
                    'action_key' => sanitize_key((string) ($bundle['action_key'] ?? '')),
                    'action_label' => sanitize_text_field((string) ($bundle['action_label'] ?? '')),
                    'bundle_type' => sanitize_key((string) ($bundle['bundle_type'] ?? '')),
                    'priority_label' => sanitize_text_field((string) ($bundle['priority_label'] ?? 'Medium')),
                    'approval_state' => sanitize_key((string) ($bundle['approval_state'] ?? 'suggested')),
                    'execution_state' => sanitize_key((string) ($bundle['execution_state'] ?? 'open')),
                ),
            );
        }
        return array();
    }

    private function aysa_adjustments_is_site_audit_bundle_action_key($action_key) {
        $catalog = $this->aysa_adjustments_site_audit_action_bundle_catalog();
        return isset($catalog[sanitize_key((string) $action_key)]);
    }

    private function aysa_adjustments_update_site_audit_action_bundle_state($project_id, $action_key, $changes = array()) {
        $project_id = intval($project_id);
        $action_key = sanitize_key((string) $action_key);
        if ($project_id <= 0 || $action_key === '' || !$this->aysa_adjustments_is_site_audit_bundle_action_key($action_key)) {
            return false;
        }

        $state = get_option($this->aysa_technical_site_audit_state_key($project_id), array());
        if (!is_array($state) || empty($state)) {
            $latest_version = $this->aysa_sf_site_audit_get_latest_version_row($project_id);
            if (is_array($latest_version) && !empty($latest_version)) {
                $state = $this->aysa_sf_site_audit_build_state_from_version_row($latest_version);
            }
        }
        if (!is_array($state) || empty($state)) {
            return false;
        }

        $action_bundles = isset($state['action_bundles']) && is_array($state['action_bundles']) ? $state['action_bundles'] : array();
        $bundle_state = isset($action_bundles[$action_key]) && is_array($action_bundles[$action_key]) ? $action_bundles[$action_key] : array();

        foreach ((array) $changes as $key => $value) {
            switch ((string) $key) {
                case 'approval_state':
                    $normalized = sanitize_key((string) $value);
                    if (in_array($normalized, array('suggested', 'approved', 'preparing', 'prepared', 'applying', 'applied', 'failed', 'manual_needed', 'verifying', 'verification_pending', 'verified', 'dismissed'), true)) {
                        $bundle_state['approval_state'] = $normalized;
                    }
                    break;
                case 'execution_state':
                    $normalized = sanitize_key((string) $value);
                    if (in_array($normalized, array('open', 'accepted', 'preparing', 'prepared', 'applying', 'applied', 'failed', 'manual_needed', 'verifying', 'verification_pending', 'verified', 'ignored'), true)) {
                        $bundle_state['execution_state'] = $normalized;
                    }
                    break;
                case 'execution_package_id':
                    $bundle_state['execution_package_id'] = sanitize_text_field((string) $value);
                    break;
                case 'metadata_execution_package':
                case 'image_alt_execution_package':
                case 'canonical_execution_package':
                case 'last_prepare_result':
                case 'last_apply_result':
                case 'last_verify_result':
                    $bundle_state[(string) $key] = is_array($value) ? $value : array();
                    break;
                case 'worker_run_id':
                case 'worker_task_id':
                    $bundle_state[(string) $key] = intval($value);
                    break;
                case 'last_error':
                    $bundle_state['last_error'] = sanitize_text_field((string) $value);
                    break;
                case 'last_prepared_summary':
                    $bundle_state['last_prepared_summary'] = sanitize_text_field((string) $value);
                    break;
                case 'last_prepared_at':
                case 'last_applied_at':
                case 'last_verified_at':
                case 'approved_at':
                case 'dismissed_at':
                case 'last_sent_to_agent8_at':
                    $bundle_state[(string) $key] = sanitize_text_field((string) $value);
                    break;
            }
        }

        if (empty($bundle_state['approval_state'])) {
            $bundle_state['approval_state'] = 'suggested';
        }

        $action_bundles[$action_key] = $bundle_state;
        $state['action_bundles'] = $action_bundles;
        $state['updated_at'] = current_time('mysql');
        update_option($this->aysa_technical_site_audit_state_key($project_id), $state, false);
        $this->aysa_sf_site_audit_persist_version($project_id, $state);

        return true;
    }

    private function aysa_technical_site_audit_build_metadata_execution_package($project_id, $snapshot, $bundle, $package_id) {
        $project_id = intval($project_id);
        $snapshot = is_array($snapshot) ? $snapshot : array();
        $bundle = is_array($bundle) ? $bundle : array();
        $package_id = sanitize_text_field((string) $package_id);
        $items = array();
        $skipped = array();
        $seen = array();
        $seen_proposals = array();
        $issues = isset($snapshot['rows']) && is_array($snapshot['rows']) ? array_values($snapshot['rows']) : array();

        foreach ($issues as $issue) {
            if (!is_array($issue) || sanitize_key((string) ($issue['category'] ?? '')) !== 'metadata') {
                continue;
            }
            $issue_type = sanitize_key((string) ($issue['issue_type'] ?? $issue['key'] ?? ''));
            if ($issue_type === '' || strpos($issue_type, 'h1') !== false || strpos($issue_type, 'h2') !== false) {
                continue;
            }
            $field = '';
            if (strpos($issue_type, 'meta_description') !== false) {
                $field = 'meta_description';
            } elseif (strpos($issue_type, 'page_title') !== false || strpos($issue_type, 'title') !== false) {
                $field = 'meta_title';
            }
            if ($field === '') {
                continue;
            }

            $rows = isset($issue['affected_page_rows']) && is_array($issue['affected_page_rows']) ? array_values($issue['affected_page_rows']) : array();
            if (empty($rows) && isset($issue['examples']) && is_array($issue['examples'])) {
                $rows = array_values($issue['examples']);
            }
            foreach ($rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $url = esc_url_raw((string) ($row['url'] ?? ''));
                if ($url === '') {
                    continue;
                }
                $post_id = url_to_postid($url);
                if ($post_id <= 0) {
                    $skipped[] = array('url' => $url, 'field' => $field, 'reason' => 'no_clear_wordpress_post_mapping', 'old_value' => '', 'proposed_value' => '');
                    continue;
                }
                $post = get_post($post_id);
                if (!$post || !in_array((string) $post->post_status, array('publish', 'draft', 'private'), true)) {
                    $skipped[] = array('url' => $url, 'field' => $field, 'post_id' => $post_id, 'reason' => 'post_not_editable', 'old_value' => '', 'proposed_value' => '');
                    continue;
                }
                $item_key = $post_id . ':' . $field;
                if (isset($seen[$item_key])) {
                    continue;
                }
                $seen[$item_key] = true;
                $current = $field === 'meta_title'
                    ? $this->aysa_technical_site_audit_current_post_meta_title($post_id, $post)
                    : $this->aysa_technical_site_audit_current_post_meta_description($post_id, $post);
                if (strpos($issue_type, 'missing') !== false) {
                    $current = '';
                }
                $proposed = $field === 'meta_title'
                    ? $this->aysa_technical_site_audit_proposed_meta_title($post)
                    : $this->aysa_technical_site_audit_proposed_meta_description($post);
                $quality = $this->aysa_technical_site_audit_metadata_candidate_quality($field, $current, $proposed, $issue_type, $post);
                $decision = (string) ($quality['decision'] ?? '');
                if ($decision === 'preserve') {
                    $skipped[] = array('url' => $url, 'field' => $field, 'post_id' => $post_id, 'reason' => 'already_optimized', 'old_value' => sanitize_text_field((string) $current), 'proposed_value' => sanitize_text_field((string) $proposed), 'quality_score' => $quality['quality_score'], 'candidate_quality' => $quality);
                    continue;
                }
                if ($decision === 'manual_review') {
                    $skipped[] = array('url' => $url, 'field' => $field, 'post_id' => $post_id, 'reason' => (string) ($quality['overwrite_reason'] ?? 'manual_review_needed'), 'old_value' => sanitize_text_field((string) $current), 'proposed_value' => sanitize_text_field((string) $proposed), 'quality_score' => $quality['quality_score'], 'candidate_quality' => $quality);
                    continue;
                }
                if ($quality['classification'] === 'already_optimized') {
                    $skipped[] = array('url' => $url, 'field' => $field, 'post_id' => $post_id, 'reason' => 'already_optimized', 'old_value' => sanitize_text_field((string) $current), 'proposed_value' => sanitize_text_field((string) $proposed), 'quality_score' => $quality['quality_score'], 'candidate_quality' => $quality);
                    continue;
                }
                $validation = $this->aysa_technical_site_audit_validate_metadata_proposal($field, $proposed);
                if (!$validation['ok']) {
                    $skipped[] = array('url' => $url, 'field' => $field, 'post_id' => $post_id, 'reason' => $validation['reason'], 'old_value' => sanitize_text_field((string) $current), 'proposed_value' => sanitize_text_field((string) $proposed), 'quality_score' => $quality['quality_score'], 'candidate_quality' => $quality);
                    continue;
                }
                $proposal_key = $field . ':' . strtolower($proposed);
                if (isset($seen_proposals[$proposal_key])) {
                    $skipped[] = array('url' => $url, 'field' => $field, 'post_id' => $post_id, 'reason' => 'duplicate_proposed_value_in_batch', 'old_value' => sanitize_text_field((string) $current), 'proposed_value' => sanitize_text_field((string) $proposed));
                    continue;
                }
                $seen_proposals[$proposal_key] = true;
                $items[] = array(
                    'url' => $url,
                    'post_id' => $post_id,
                    'entity_key' => 'post:' . $post_id,
                    'post_type' => sanitize_key((string) $post->post_type),
                    'field' => $field,
                    'issue_type' => $issue_type,
                    'issue_id' => sanitize_text_field((string) ($issue['issue_id'] ?? '')),
                    'old_value' => sanitize_text_field((string) $current),
                    'proposed_value' => sanitize_text_field((string) $proposed),
                    'confidence' => intval($quality['quality_score'] ?? 70),
                    'classification' => sanitize_key((string) ($quality['classification'] ?? 'safe_auto_apply')),
                    'overwrite_decision' => sanitize_key((string) ($quality['decision'] ?? 'rewrite')),
                    'overwrite_reason' => sanitize_text_field((string) ($quality['overwrite_reason'] ?? 'metadata_weak_or_audit_flagged')),
                    'quality_flags' => isset($quality['quality_flags']) && is_array($quality['quality_flags']) ? array_values($quality['quality_flags']) : array(),
                    'inclusion_reason' => sanitize_text_field((string) ($quality['inclusion_reason'] ?? 'metadata_issue_from_site_audit')),
                    'quality_notes' => isset($quality['notes']) && is_array($quality['notes']) ? array_values($quality['notes']) : array(),
                    'status' => 'ready_for_apply',
                );
            }
        }

        return array(
            'package_id' => $package_id,
            'project_id' => $project_id,
            'action_key' => 'prepare_metadata_remediation',
            'issue_family' => 'metadata',
            'bundle_type' => sanitize_key((string) ($bundle['bundle_type'] ?? 'metadata')),
            'created_at' => current_time('mysql'),
            'items' => array_values($items),
            'candidates' => array_values($items),
            'row_results' => array(),
            'skipped' => array_values($skipped),
            'items_count' => count($items),
            'skipped_count' => count($skipped),
            'total_candidates' => count($items) + count($skipped),
            'safe_auto_apply_count' => count($items),
            'manual_review_needed_count' => count($skipped),
            'no_valid_target_count' => 0,
        );
    }

    private function aysa_technical_site_audit_build_package_adapter_context($project_id, $action_key, $payload = array()) {
        $project_id = intval($project_id);
        $action_key = sanitize_key((string) $action_key);
        $snapshot = array();
        $payload = is_array($payload) ? $payload : array();
        $payload_snapshot = isset($payload['technical_site_audit']) && is_array($payload['technical_site_audit']) ? $payload['technical_site_audit'] : array();
        if (!empty($payload_snapshot)) {
            $snapshot = $payload_snapshot;
        }
        if (empty($snapshot)) {
            $snapshot = $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id);
        }
        $normalized_snapshot = json_decode(wp_json_encode($snapshot), true);
        if (is_array($normalized_snapshot)) {
            $snapshot = $normalized_snapshot;
        }
        $bundle = $this->aysa_technical_site_audit_find_action_bundle($snapshot, $action_key);
        $context = array(
            'source' => 'wordpress_adapter_context',
            'action_key' => $action_key,
        );

        if ($action_key === 'prepare_metadata_remediation') {
            $posts = array();
            $adapter_rows = array();
            foreach ((array) ($snapshot['rows'] ?? array()) as $issue) {
                $issue = is_object($issue) ? (array) $issue : $issue;
                if (!is_array($issue)) {
                    continue;
                }
                $affected = isset($issue['affected_page_rows']) && is_array($issue['affected_page_rows'])
                    ? $issue['affected_page_rows']
                    : array();
                if (empty($affected) && isset($issue['examples']) && is_array($issue['examples'])) {
                    $affected = $issue['examples'];
                }
                foreach ($affected as $row) {
                    $row = is_object($row) ? (array) $row : $row;
                    if (!is_array($row)) {
                        continue;
                    }
                    $row['site_audit_issue_type'] = sanitize_key((string) ($issue['issue_type'] ?? $issue['key'] ?? ''));
                    $row['site_audit_issue_id'] = sanitize_text_field((string) ($issue['issue_id'] ?? ''));
                    $row['site_audit_category'] = sanitize_key((string) ($issue['category'] ?? ''));
                $adapter_rows[] = $row;
            }
        }
            foreach ((array) ($snapshot['pages'] ?? array()) as $page_row) {
                $page_row = is_object($page_row) ? (array) $page_row : $page_row;
                if (!is_array($page_row)) {
                    continue;
                }
            $url = $this->aysa_technical_site_audit_row_url($page_row);
            if ($url === '') {
                continue;
            }
            $page_row['site_audit_issue_type'] = 'metadata_route_evidence';
            $page_row['site_audit_issue_id'] = '';
                $page_row['site_audit_category'] = 'metadata';
                $adapter_rows[] = $page_row;
            }
            $post_types = array_values(get_post_types(array('public' => true), 'names'));
            $post_ids = get_posts(array(
                'post_type' => $post_types,
                'post_status' => array('publish'),
                'posts_per_page' => 1000,
                'fields' => 'ids',
                'orderby' => 'ID',
                'order' => 'DESC',
                'no_found_rows' => true,
                'update_post_meta_cache' => false,
                'update_post_term_cache' => false,
            ));
            foreach ((array) $post_ids as $post_id) {
                $permalink = get_permalink(intval($post_id));
                if (!$permalink) {
                    continue;
                }
                $adapter_rows[] = array(
                    'url' => esc_url_raw((string) $permalink),
                    'site_audit_issue_type' => 'metadata_route_evidence',
                    'site_audit_issue_id' => '',
                    'site_audit_category' => 'metadata',
                );
            }
            foreach ($adapter_rows as $row) {
                $url = $this->aysa_technical_site_audit_row_url($row);
                if ($url === '' || isset($posts[$url])) {
                    continue;
                }
                if (!$this->aysa_agent8v3_metadata_route_evidence_url_is_indexable($url)) {
                    continue;
                }
                $post_id = url_to_postid($url);
                if ($post_id <= 0) {
                    $posts[$url] = array('post_id' => 0, 'reason' => 'no_clear_wordpress_post_mapping');
                    continue;
                }
                if (!$this->aysa_agent8v3_metadata_route_evidence_url_is_indexable($url, $post_id)) {
                    continue;
                }
                $post = get_post($post_id);
                if (!$post) {
                    $posts[$url] = array('post_id' => $post_id, 'reason' => 'post_not_found');
                    continue;
                }
                $primary_keyword = $this->aysa_technical_site_audit_primary_keyword($post_id);
                $posts[$url] = array(
                    'post_id' => $post_id,
                    'post_type' => sanitize_key((string) $post->post_type),
                    'post_status' => sanitize_key((string) $post->post_status),
                    'post_title' => sanitize_text_field((string) $post->post_title),
                    'post_excerpt' => sanitize_textarea_field((string) $post->post_excerpt),
                    'post_content' => sanitize_textarea_field(mb_substr(wp_strip_all_tags((string) $post->post_content), 0, 3000)),
                    'current_meta_title' => $this->aysa_technical_site_audit_current_post_meta_title($post_id, $post),
                    'current_meta_description' => $this->aysa_technical_site_audit_current_post_meta_description($post_id, $post),
                    'current_provider_meta_title' => sanitize_text_field((string) get_post_meta($post_id, '_aysa_seo_title', true)),
                    'current_provider_meta_description' => sanitize_text_field((string) get_post_meta($post_id, '_aysa_seo_description', true)),
                    'metadata_provider' => 'aysa',
                    'readback_available' => true,
                    'rollback_available' => true,
                    'backup_available' => true,
                    'restore_available' => true,
                    'billing_action_key' => 'agent8v3_metadata_apply',
                    'idempotency_supported' => true,
                    'primary_keyword' => $primary_keyword,
                    'secondary_keywords' => $this->aysa_technical_site_audit_secondary_keywords($post_id, $primary_keyword),
                    'site_name' => sanitize_text_field((string) get_bloginfo('name')),
                    'permalink' => esc_url_raw((string) get_permalink($post)),
                );
            }
            $context['metadata_posts'] = $posts;
        } elseif ($action_key === 'prepare_response_code_review') {
            $hints = array();
            $rows = $this->aysa_technical_site_audit_collect_bundle_rows($snapshot, $bundle);
            if (empty($rows)) {
                $technical_errors = $this->aysa_adjustments_get_technical_errors_snapshot($project_id);
                $rows = isset($technical_errors['rows']) && is_array($technical_errors['rows']) ? array_values($technical_errors['rows']) : array();
            }
            $context['redirect_rows'] = array_values($rows);
            foreach ($rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $source = $this->aysa_technical_site_audit_row_url($row);
                if ($source === '' || isset($hints[$source])) {
                    continue;
                }
                $hint = array();
                $path = (string) wp_parse_url($source, PHP_URL_PATH);
                $query = (string) wp_parse_url($source, PHP_URL_QUERY);
                if ($query !== '' && $path !== '') {
                    $target = home_url(untrailingslashit($path) . '/');
                    if ($this->aysa_technical_site_audit_redirect_target_is_safe($source, $target) && $this->aysa_technical_site_audit_url_looks_live($target)) {
                        $hint['query_stripped_live_url'] = esc_url_raw($target);
                        $hint['query_stripped_live_url_reason'] = 'same_path_without_query_is_live';
                    }
                }
                $slug = sanitize_title(basename(untrailingslashit($path)));
                if ($slug !== '') {
                    $post_types = array_values(get_post_types(array('public' => true), 'names'));
                    $post = get_page_by_path($slug, OBJECT, $post_types);
                    if ($post instanceof WP_Post && (string) $post->post_status === 'publish') {
                        $target = get_permalink($post);
                        if ($target && $this->aysa_technical_site_audit_redirect_target_is_safe($source, $target)) {
                            $hint['exact_slug_permalink'] = esc_url_raw((string) $target);
                            $hint['exact_slug_permalink_reason'] = 'exact_slug_matches_live_wordpress_permalink';
                        }
                    }
                }
                $hints[$source] = $hint;
            }
            $context['redirect_target_hints'] = $hints;
        } elseif ($action_key === 'prepare_internal_linking_plan') {
            $context['internal_linking_fallback_rows'] = $this->aysa_technical_site_audit_discover_internal_linking_rows_from_wp($project_id);
        } elseif ($action_key === 'prepare_image_alt_text') {
            $image_items = array();
            $rows = $this->aysa_technical_site_audit_collect_bundle_rows($snapshot, $bundle);
            foreach ((array) $rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $image_url = esc_url_raw((string) ($row['image_url'] ?? $row['src'] ?? $row['address'] ?? $row['url'] ?? ''));
                if ($image_url === '' || isset($image_items[$image_url])) {
                    continue;
                }
                $attachment_id = $this->aysa_technical_site_audit_attachment_id_from_image_url($image_url);
                $page_url = esc_url_raw((string) ($row['page_url'] ?? $row['page'] ?? $row['from'] ?? $row['source'] ?? ''));
                $page_id = $page_url !== '' ? url_to_postid($page_url) : 0;
                if ($page_id <= 0 && $attachment_id > 0) {
                    $attachment_post = get_post($attachment_id);
                    $parent_id = $attachment_post instanceof WP_Post ? intval($attachment_post->post_parent) : 0;
                    if ($parent_id > 0 && get_post_status($parent_id) === 'publish') {
                        $page_id = $parent_id;
                        $page_url = esc_url_raw((string) get_permalink($parent_id));
                    }
                }
                $page_post = $page_id > 0 ? get_post($page_id) : null;
                $primary_keyword = $page_id > 0 ? $this->aysa_technical_site_audit_primary_keyword($page_id) : '';
                if ($primary_keyword === '' && $page_post instanceof WP_Post) {
                    $primary_keyword = sanitize_text_field((string) $page_post->post_title);
                }
                $mime = $attachment_id > 0 ? (string) get_post_mime_type($attachment_id) : '';
                $language_context = $this->aysa_technical_site_audit_resolve_image_alt_language($project_id, $page_id);
                $aysa_alt_generated_at = $attachment_id > 0 ? sanitize_text_field((string) get_post_meta($attachment_id, '_aysa_image_alt_generated_at', true)) : '';
                $image_items[$image_url] = array(
                    'attachment_id' => intval($attachment_id),
                    'existing_alt' => $attachment_id > 0 ? sanitize_text_field((string) get_post_meta($attachment_id, '_wp_attachment_image_alt', true)) : '',
                    'aysa_alt_generated' => $aysa_alt_generated_at !== '',
                    'aysa_alt_generated_at' => $aysa_alt_generated_at,
                    'page_url' => $page_url,
                    'page_title' => $page_post instanceof WP_Post ? sanitize_text_field((string) $page_post->post_title) : '',
                    'primary_keyword' => $primary_keyword,
                    'secondary_keywords' => $page_id > 0 ? $this->aysa_technical_site_audit_secondary_keywords($page_id, $primary_keyword) : array(),
                    'image_filename' => sanitize_text_field(basename((string) wp_parse_url($image_url, PHP_URL_PATH))),
                    'language' => sanitize_text_field((string) ($language_context['language'] ?? 'ro')),
                    'language_source' => sanitize_key((string) ($language_context['source'] ?? 'site_locale_fallback')),
                    'media_optimization_eligible' => in_array($mime, array('image/jpeg', 'image/png'), true),
                    'mime_type' => sanitize_text_field($mime),
                );
            }
            $fallback_image_alt_inventory = $this->aysa_technical_site_audit_discover_image_alt_items_from_wp($project_id);
            foreach ($fallback_image_alt_inventory as $image_url => $fallback_item) {
                if (!isset($image_items[$image_url]) && is_array($fallback_item)) {
                    $image_items[$image_url] = $fallback_item;
                } elseif (is_array($fallback_item) && intval($image_items[$image_url]['attachment_id'] ?? 0) <= 0 && intval($fallback_item['attachment_id'] ?? 0) > 0) {
                    $image_items[$image_url] = $fallback_item;
                }
            }
            $context['image_alt_items'] = $image_items;
            $context['image_alt_inventory_summary'] = $this->aysa_technical_site_audit_image_alt_inventory_summary($fallback_image_alt_inventory);
        } elseif ($action_key === 'prepare_indexability_review') {
            $posts = array();
            $rows = $this->aysa_technical_site_audit_collect_bundle_rows($snapshot, $bundle);
            foreach ((array) $rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $url = $this->aysa_technical_site_audit_row_url($row);
                if ($url === '' || isset($posts[$url])) {
                    continue;
                }
                $post_id = url_to_postid($url);
                if ($post_id <= 0) {
                    $posts[$url] = array('post_id' => 0, 'reason' => 'no_clear_wordpress_post_mapping');
                    continue;
                }
                $post = get_post($post_id);
                if (!$post instanceof WP_Post) {
                    $posts[$url] = array('post_id' => $post_id, 'reason' => 'post_not_found');
                    continue;
                }
                $posts[$url] = array(
                    'post_id' => $post_id,
                    'post_type' => sanitize_key((string) $post->post_type),
                    'post_status' => sanitize_key((string) $post->post_status),
                    'page_title' => sanitize_text_field((string) $post->post_title),
                    'permalink' => esc_url_raw((string) get_permalink($post)),
                    'current_canonical_url' => $this->aysa_technical_site_audit_current_post_canonical_url($post_id),
                    'canonical_provider' => $this->aysa_technical_site_audit_detect_canonical_provider($post_id),
                    'gsc_signals' => array(),
                );
            }
            $has_mapped_posts = false;
            foreach ($posts as $post_context) {
                if (is_array($post_context) && intval($post_context['post_id'] ?? 0) > 0) {
                    $has_mapped_posts = true;
                    break;
                }
            }
            if (!$has_mapped_posts) {
                $posts = $this->aysa_technical_site_audit_discover_canonical_posts_from_wp($project_id);
            }
            $context['canonical_posts'] = $posts;
        }

        return $context;
    }

    private function aysa_technical_site_audit_resolve_image_alt_language($project_id, $page_id = 0) {
        $project_id = intval($project_id);
        $page_id = intval($page_id);

        if ($page_id > 0) {
            $page_language = $this->aysa_technical_site_audit_normalize_language_code((string) get_post_meta($page_id, 'aysa_language_optimize', true), '');
            if ($page_language !== '') {
                return array(
                    'language' => $page_language,
                    'source' => 'page_language_optimize',
                );
            }
        }

        if ($project_id > 0) {
            $project_settings = get_option('aysa_wizard_seo-settings_project_' . $project_id, array());
            if (is_array($project_settings)) {
                $project_language = $this->aysa_technical_site_audit_normalize_language_code((string) ($project_settings['primary_language'] ?? ($project_settings['language_optimize'] ?? '')), '');
                if ($project_language !== '') {
                    return array(
                        'language' => $project_language,
                        'source' => 'setup_seo_profile_project',
                    );
                }
            }
        }

        $candidate_user_ids = array();
        $external_user_id = intval($this->aysa_get_authenticated_external_user_id());
        if ($external_user_id > 0) {
            $candidate_user_ids[] = $external_user_id;
        }
        $shared_user_id = intval(get_option('aysa_user_id', 0));
        if ($shared_user_id > 0) {
            $candidate_user_ids[] = $shared_user_id;
        }
        $current_user_id = function_exists('get_current_user_id') ? intval(get_current_user_id()) : 0;
        if ($current_user_id > 0) {
            $candidate_user_ids[] = $current_user_id;
        }

        foreach (array_values(array_unique(array_filter($candidate_user_ids))) as $user_id) {
            $seo_settings = get_option('aysa_wizard_seo-settings_' . intval($user_id), array());
            if (!is_array($seo_settings)) {
                continue;
            }
            $profile_language = $this->aysa_technical_site_audit_normalize_language_code((string) ($seo_settings['primary_language'] ?? ($seo_settings['language_optimize'] ?? '')), '');
            if ($profile_language !== '') {
                return array(
                    'language' => $profile_language,
                    'source' => 'setup_seo_profile',
                );
            }
        }

        $global_language = $this->aysa_technical_site_audit_normalize_language_code((string) get_option('aysa_language', get_locale()), 'ro');
        return array(
            'language' => $global_language,
            'source' => 'site_locale_fallback',
        );
    }

    private function aysa_technical_site_audit_normalize_language_code($value, $default = 'ro') {
        $value = strtolower(str_replace('_', '-', trim((string) $value)));
        if ($value === '') {
            return (string) $default;
        }
        $base = explode('-', $value);
        $base = is_array($base) && isset($base[0]) ? strtolower(trim((string) $base[0])) : $value;
        $map = array(
            'ro' => 'ro',
            'romanian' => 'ro',
            'romana' => 'ro',
            'română' => 'ro',
            'romanian language' => 'ro',
            'en' => 'en',
            'english' => 'en',
            'engleza' => 'en',
            'engleză' => 'en',
            'bg' => 'bg',
            'bulgarian' => 'bg',
            'bulgara' => 'bg',
            'bulgară' => 'bg',
            'de' => 'de',
            'german' => 'de',
            'germana' => 'de',
            'germană' => 'de',
            'fr' => 'fr',
            'french' => 'fr',
            'franceza' => 'fr',
            'franceză' => 'fr',
            'es' => 'es',
            'spanish' => 'es',
            'spaniola' => 'es',
            'spaniolă' => 'es',
            'it' => 'it',
            'italian' => 'it',
            'italiana' => 'it',
            'italiană' => 'it',
        );
        if (isset($map[$value])) {
            return $map[$value];
        }
        if (isset($map[$base])) {
            return $map[$base];
        }
        if (preg_match('/^[a-z]{2}$/', $base)) {
            return $base;
        }
        return (string) $default;
    }

    private function aysa_technical_site_audit_collect_metadata_adapter_rows($snapshot) {
        $snapshot = is_array($snapshot) ? $snapshot : array();
        $rows = array();
        foreach ((array) ($snapshot['rows'] ?? array()) as $issue) {
            if (is_object($issue)) {
                $issue = (array) $issue;
            }
            if (!is_array($issue)) {
                continue;
            }
            $issue_type = sanitize_key((string) ($issue['issue_type'] ?? $issue['key'] ?? ''));
            $affected = isset($issue['affected_page_rows']) && is_array($issue['affected_page_rows'])
                ? $issue['affected_page_rows']
                : array();
            if (empty($affected) && isset($issue['examples']) && is_array($issue['examples'])) {
                $affected = $issue['examples'];
            }
            foreach ($affected as $row) {
                if (is_object($row)) {
                    $row = (array) $row;
                }
                if (!is_array($row)) {
                    continue;
                }
                $row['site_audit_issue_type'] = $issue_type;
                $row['site_audit_issue_id'] = sanitize_text_field((string) ($issue['issue_id'] ?? ''));
                $row['site_audit_category'] = 'metadata';
                $rows[] = $row;
            }
        }
        return $rows;
    }

	    private function aysa_technical_site_audit_build_redirect_execution_package($project_id, $snapshot, $bundle, $package_id) {
	        $project_id = intval($project_id);
	        $snapshot = is_array($snapshot) ? $snapshot : array();
	        $bundle = is_array($bundle) ? $bundle : array();
        $package_id = sanitize_text_field((string) $package_id);
        $rows = $this->aysa_technical_site_audit_collect_bundle_rows($snapshot, $bundle);
        if (empty($rows)) {
            $technical_errors = $this->aysa_adjustments_get_technical_errors_snapshot($project_id);
            $rows = isset($technical_errors['rows']) && is_array($technical_errors['rows']) ? array_values($technical_errors['rows']) : array();
        }

        $candidates = array();
        $seen = array();
        $safe = 0;
        $manual = 0;
        $no_target = 0;
	        foreach ((array) $rows as $row) {
	            if (!is_array($row)) {
	                continue;
	            }
	            $candidate = $this->aysa_technical_site_audit_build_redirect_candidate($row);
	            $source = sanitize_text_field((string) ($candidate['source_url'] ?? ''));
	            if ($source === '' || $source === '-' || isset($seen[$source])) {
	                continue;
	            }
	            $seen[$source] = true;
            if (($candidate['classification'] ?? '') === 'safe_auto_apply') {
                $safe++;
            } elseif (($candidate['classification'] ?? '') === 'manual_review_needed') {
                $manual++;
            } else {
                $no_target++;
            }
            $candidates[] = $candidate;
        }

        return array(
            'package_id' => $package_id,
            'project_id' => $project_id,
            'action_key' => 'prepare_response_code_review',
            'issue_family' => 'response_codes_redirects',
            'created_at' => current_time('mysql'),
            'source' => empty($rows) ? 'site_audit_empty' : 'site_audit_or_technical_redirect_rows',
            'candidates' => array_values($candidates),
            'row_results' => array(),
            'total_candidates' => count($candidates),
            'safe_auto_apply_count' => $safe,
            'manual_review_needed_count' => $manual,
	            'no_valid_target_count' => $no_target,
	        );
	    }

	    private function aysa_technical_site_audit_clean_redirect_execution_package($package) {
	        if (!is_array($package) || empty($package)) {
	            return array();
	        }

	        $candidates = array();
	        $seen = array();
	        $safe = 0;
	        $manual = 0;
	        $no_target = 0;
	        foreach ((array) ($package['candidates'] ?? array()) as $candidate) {
	            if (!is_array($candidate)) {
	                continue;
	            }
	            $source = esc_url_raw((string) ($candidate['source_url'] ?? ''));
	            if ($source === '' || $source === '-' || isset($seen[$source])) {
	                continue;
	            }
	            $seen[$source] = true;
	            $classification = sanitize_key((string) ($candidate['classification'] ?? ''));
	            if ($classification === 'safe_auto_apply') {
	                $safe++;
	            } elseif ($classification === 'manual_review_needed') {
	                $manual++;
	            } else {
	                $no_target++;
	            }
	            $candidates[] = $candidate;
	        }

	        $package['candidates'] = array_values($candidates);
	        $package['total_candidates'] = count($candidates);
	        $package['safe_auto_apply_count'] = $safe;
	        $package['manual_review_needed_count'] = $manual;
	        $package['no_valid_target_count'] = $no_target;

	        return $package;
	    }

    private function aysa_technical_site_audit_build_redirect_candidate($row) {
        $row = is_array($row) ? $row : array();
        $source = $this->aysa_technical_site_audit_row_url($row);
        if ($source === '') {
            $source = esc_url_raw((string) ($row['source_url'] ?? ''));
        }
        $status_code = 0;
        foreach (array('status_code', 'response_code', 'status', 'http_status') as $key) {
            $status_code = intval($row[$key] ?? 0);
            if ($status_code > 0) {
                break;
            }
        }
        $issue_type = sanitize_key((string) ($row['site_audit_issue_type'] ?? $row['issue_type'] ?? $row['key'] ?? $row['suggested_action'] ?? 'response_code_review'));
        if (!$this->aysa_technical_site_audit_redirect_source_is_safe($source)) {
            return array(
                'source_url' => esc_url_raw($source),
                'detected_issue_type' => $issue_type,
                'current_status_code' => $status_code,
                'proposed_target_url' => '',
                'target_resolution_method' => 'blocked_protected_source',
                'confidence' => 0,
                'classification' => 'no_valid_target',
                'safe_to_apply' => false,
                'manual_review_required' => true,
                'reason' => 'protected_wordpress_or_system_path_not_safe_for_redirect_auto_apply',
            );
        }
        $resolution = $this->aysa_technical_site_audit_resolve_redirect_target($source, $row);
        $target = esc_url_raw((string) ($resolution['target_url'] ?? ''));
        $confidence = intval($resolution['confidence'] ?? 0);
        $classification = 'no_valid_target';
        if ($target !== '' && $confidence >= 90) {
            $classification = 'safe_auto_apply';
        } elseif ($target !== '') {
            $classification = 'manual_review_needed';
        }

        return array(
            'source_url' => esc_url_raw($source),
            'detected_issue_type' => $issue_type,
            'current_status_code' => $status_code,
            'proposed_target_url' => $target,
            'target_resolution_method' => sanitize_key((string) ($resolution['method'] ?? 'none')),
            'confidence' => $confidence,
            'confidence_tier' => sanitize_key((string) ($resolution['confidence_tier'] ?? ($confidence >= 90 ? 'high' : ($confidence > 0 ? 'medium' : 'low')))),
            'classification' => $classification,
            'safe_to_apply' => $classification === 'safe_auto_apply',
            'manual_review_required' => $classification !== 'safe_auto_apply',
            'reason' => sanitize_text_field((string) ($resolution['reason'] ?? '')),
        );
    }

    private function aysa_technical_site_audit_resolve_redirect_target($source, $row) {
        $source = esc_url_raw((string) $source);
        $row = is_array($row) ? $row : array();
        foreach (array('suggested_target_url', 'target_url', 'redirect_url', 'destination', 'final_url', 'final_address', 'canonical_url') as $key) {
            $target = esc_url_raw((string) ($row[$key] ?? ''));
            if ($target !== '' && $this->aysa_technical_site_audit_redirect_target_is_safe($source, $target)) {
                return array('target_url' => $target, 'method' => $key, 'confidence' => 95, 'confidence_tier' => 'high', 'reason' => 'explicit_target_from_audit_or_redirect_module');
            }
        }

        $path = (string) wp_parse_url($source, PHP_URL_PATH);
        $query = (string) wp_parse_url($source, PHP_URL_QUERY);
        if ($query !== '' && $path !== '') {
            $target = home_url(untrailingslashit($path) . '/');
            if ($this->aysa_technical_site_audit_redirect_target_is_safe($source, $target) && $this->aysa_technical_site_audit_url_looks_live($target)) {
                return array('target_url' => esc_url_raw($target), 'method' => 'query_stripped_live_url', 'confidence' => 92, 'confidence_tier' => 'high', 'reason' => 'same_path_without_query_is_live');
            }
        }
        $slug = sanitize_title(basename(untrailingslashit($path)));
        if ($slug !== '') {
            $post_types = array_values(get_post_types(array('public' => true), 'names'));
            $post = get_page_by_path($slug, OBJECT, $post_types);
            if ($post instanceof WP_Post && (string) $post->post_status === 'publish') {
                $target = get_permalink($post);
                if ($target && $this->aysa_technical_site_audit_redirect_target_is_safe($source, $target)) {
                    return array('target_url' => esc_url_raw($target), 'method' => 'exact_slug_permalink_match', 'confidence' => 90, 'confidence_tier' => 'high', 'reason' => 'exact_slug_matches_live_wordpress_permalink');
                }
            }
        }

        return array('target_url' => '', 'method' => 'none', 'confidence' => 0, 'confidence_tier' => 'low', 'reason' => 'no_safe_redirect_target_found');
    }

    private function aysa_technical_site_audit_url_looks_live($url) {
        $response = wp_remote_head($url, array('timeout' => 4, 'redirection' => 2));
        if (is_wp_error($response)) {
            return false;
        }
        $code = intval(wp_remote_retrieve_response_code($response));
        return $code >= 200 && $code < 400;
    }

    private function aysa_technical_site_audit_redirect_target_is_safe($source, $target) {
        $source = esc_url_raw((string) $source);
        $target = esc_url_raw((string) $target);
        if ($source === '' || $target === '') {
            return false;
        }
        $source_path = untrailingslashit((string) wp_parse_url($source, PHP_URL_PATH));
        $target_path = untrailingslashit((string) wp_parse_url($target, PHP_URL_PATH));
        if ($source_path !== '' && $target_path !== '' && $source_path === $target_path) {
            return false;
        }
        $target_host = (string) wp_parse_url($target, PHP_URL_HOST);
        $site_host = (string) wp_parse_url(home_url('/'), PHP_URL_HOST);
        return $target_host === '' || $site_host === '' || strcasecmp($target_host, $site_host) === 0;
    }

    private function aysa_technical_site_audit_redirect_source_is_safe($source) {
        $path = strtolower((string) wp_parse_url((string) $source, PHP_URL_PATH));
        if ($path === '') {
            return false;
        }
        foreach (array('/wp-admin', '/wp-content', '/wp-includes', '/xmlrpc.php', '/wp-login.php', '/wp-json', '/.well-known') as $blocked) {
            if (strpos($path, $blocked) === 0) {
                return false;
            }
        }
        if (preg_match('#/(admin|cgi-bin|vendor|system|modules|plugins|includes|uploads?)(/|$)#', $path)) {
            return false;
        }
        return true;
    }

    private function aysa_technical_site_audit_current_post_meta_title($post_id, $post) {
        foreach (array('_aysa_meta_title', '_aysa_seo_title', '_yoast_wpseo_title', 'rank_math_title') as $key) {
            $value = trim((string) get_post_meta($post_id, $key, true));
            if ($value !== '') {
                return $value;
            }
        }
        return sanitize_text_field((string) ($post->post_title ?? ''));
    }

    private function aysa_technical_site_audit_current_post_meta_description($post_id, $post) {
        foreach (array('_aysa_meta_description', '_aysa_seo_description', '_yoast_wpseo_metadesc', 'rank_math_description') as $key) {
            $value = trim((string) get_post_meta($post_id, $key, true));
            if ($value !== '') {
                return $value;
            }
        }
        $excerpt = trim(wp_strip_all_tags((string) ($post->post_excerpt ?? '')));
        if ($excerpt !== '') {
            return sanitize_text_field(wp_trim_words($excerpt, 24, ''));
        }
        return sanitize_text_field(wp_trim_words(wp_strip_all_tags((string) ($post->post_content ?? '')), 24, ''));
    }

    private function aysa_technical_site_audit_detect_canonical_provider($post_id) {
        $post_id = intval($post_id);
        if ($post_id <= 0) {
            return '';
        }
        if (trim((string) get_post_meta($post_id, '_yoast_wpseo_canonical', true)) !== '' || is_dir(WP_PLUGIN_DIR . '/wordpress-seo')) {
            return 'yoast';
        }
        if (trim((string) get_post_meta($post_id, 'rank_math_canonical_url', true)) !== '' || is_dir(WP_PLUGIN_DIR . '/seo-by-rank-math')) {
            return 'rank_math';
        }
        return 'custom_meta';
    }

    private function aysa_technical_site_audit_current_post_canonical_url($post_id) {
        $post_id = intval($post_id);
        foreach (array('_yoast_wpseo_canonical', 'rank_math_canonical_url', 'aysa_canonical_url') as $key) {
            $value = trim((string) get_post_meta($post_id, $key, true));
            if ($value !== '') {
                return esc_url_raw($value);
            }
        }
        return '';
    }

    private function aysa_technical_site_audit_update_post_canonical_url($post_id, $target_url, $provider = '') {
        $post_id = intval($post_id);
        $target_url = esc_url_raw((string) $target_url);
        $provider = sanitize_key((string) $provider);
        if ($post_id <= 0 || $target_url === '') {
            return array('applied' => false, 'reason' => 'canonical_update_context_required');
        }
        $meta_key = '_yoast_wpseo_canonical';
        if ($provider === 'rank_math') {
            $meta_key = 'rank_math_canonical_url';
        } elseif ($provider === 'custom_meta') {
            $meta_key = 'aysa_canonical_url';
        }
        update_post_meta($post_id, $meta_key, $target_url);
        return array(
            'applied' => true,
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reviewed WordPress query: the metadata selector is required by the feature and is constrained by the surrounding post type, status, limit, or administrative scope.
            'meta_key' => $meta_key,
            'target_url' => $target_url,
        );
    }

    private function aysa_technical_site_audit_proposed_meta_title($post) {
        $title = sanitize_text_field((string) ($post->post_title ?? ''));
        $site = sanitize_text_field((string) get_bloginfo('name'));
        $candidate = $title;
        if ($site !== '' && mb_strlen($title . ' | ' . $site) <= 60) {
            $candidate = $title . ' | ' . $site;
        }
        return sanitize_text_field(mb_substr($candidate, 0, 70));
    }

    private function aysa_technical_site_audit_proposed_meta_description($post) {
        $source = trim(wp_strip_all_tags((string) ($post->post_excerpt ?? '')));
        if ($source === '') {
            $source = trim(wp_strip_all_tags((string) ($post->post_content ?? '')));
        }
        $sentences = preg_split('/(?<=[.!?])\\s+/u', trim($source), -1, PREG_SPLIT_NO_EMPTY);
        foreach ((array) $sentences as $sentence) {
            $candidate = trim(preg_replace('/\\s+/', ' ', wp_strip_all_tags((string) $sentence)));
            $length = mb_strlen($candidate);
            if ($length >= 70 && $length <= 165 && preg_match('/[.!?]$/u', $candidate)) {
                return sanitize_text_field($candidate);
            }
        }
        $fallback = $this->aysa_technical_site_audit_trim_metadata_text($source, 155);
        return $fallback !== '' && mb_strlen($fallback) >= 70 ? sanitize_text_field(rtrim($fallback, " \t\n\r\0\x0B,;-:") . '.') : '';
    }

    private function aysa_technical_site_audit_metadata_candidate_quality($field, $current, $proposed, $issue_type, $post) {
        $field = sanitize_key((string) $field);
        if ($field === 'meta_description') {
            return $this->aysa_technical_site_audit_meta_description_quality($current, $proposed, $issue_type, $post);
        }
        $current = trim((string) $current);
        $proposed = trim((string) $proposed);
        $issue_type = sanitize_key((string) $issue_type);
        $notes = array();
        $min = $field === 'meta_title' ? 25 : 70;
        $max = $field === 'meta_title' ? 60 : 160;
        $current_len = mb_strlen($current);
        $proposed_len = mb_strlen($proposed);
        if ($current !== '' && $current_len >= $min && $current_len <= $max && stripos($issue_type, 'missing') === false && stripos($issue_type, 'too_long') === false && stripos($issue_type, 'duplicate') === false) {
            return array('classification' => 'already_optimized', 'quality_score' => 95, 'inclusion_reason' => 'current_metadata_already_within_quality_bounds', 'notes' => array('current_value_within_length_bounds'));
        }
        if ($current !== '' && $current === $proposed) {
            return array('classification' => 'already_optimized', 'quality_score' => 90, 'inclusion_reason' => 'proposal_matches_current_value', 'notes' => array('no_safe_improvement_over_current_value'));
        }
        if ($proposed_len < $min) {
            $notes[] = 'proposal_shorter_than_recommended';
        }
        if ($proposed_len > $max) {
            $notes[] = 'proposal_longer_than_recommended';
        }
        $title = trim((string) ($post->post_title ?? ''));
        if ($title !== '' && stripos($proposed, mb_substr($title, 0, min(18, mb_strlen($title)))) !== false) {
            $notes[] = 'aligned_with_page_title';
        }
        $score = 70;
        if ($proposed_len >= $min && $proposed_len <= $max) {
            $score += 15;
        }
        if (in_array('aligned_with_page_title', $notes, true)) {
            $score += 10;
        }
        if ($current === '') {
            $score += 5;
        }
        $score = min(100, $score);
        return array(
            'decision' => $score >= 80 ? 'rewrite' : 'manual_review',
            'overwrite_reason' => $current === '' ? 'metadata_missing' : 'metadata_weak_or_audit_flagged',
            'classification' => $score >= 80 ? 'safe_auto_apply' : 'manual_review_needed',
            'quality_score' => $score,
            'inclusion_reason' => $current === '' ? 'metadata_missing' : 'metadata_weak_or_audit_flagged',
            'quality_flags' => array(),
            'notes' => $notes,
        );
    }

    private function aysa_technical_site_audit_meta_description_quality($current, $proposed, $issue_type, $post) {
        $current = trim((string) $current);
        $issue_type = sanitize_key((string) $issue_type);
        $flags = array();
        $notes = array();
        $primary_keyword = trim((string) ($post->primary_keyword ?? $post->main_keyword ?? ''));
        $secondary_keywords = $this->aysa_technical_site_audit_keyword_list($post->secondary_keywords ?? array(), $primary_keyword);
        $current_len = mb_strlen($current);
        $has_keyword_context = $primary_keyword !== '' || !empty($secondary_keywords);

        if ($current === '') {
            return $this->aysa_technical_site_audit_rewrite_decision('missing_current_description', 98, array('missing'), array('current_description_missing'));
        }
        if ($current_len < 105) {
            return $this->aysa_technical_site_audit_rewrite_decision('current_description_too_short', 96, array('too_short'), array('current_length_below_hard_floor'));
        }
        if ($current_len > 175) {
            return $this->aysa_technical_site_audit_rewrite_decision('current_description_too_long', 96, array('too_long'), array('current_length_above_hard_ceiling'));
        }
        if (strpos($issue_type, 'duplicate') !== false) {
            return $this->aysa_technical_site_audit_rewrite_decision('duplicate_description_flagged_by_site_audit', 94, array('duplicate'), array('site_audit_duplicate_signal'));
        }

        if ($current_len >= 120 && $current_len <= 160) {
            $flags[] = 'length_in_preserve_band';
        } else {
            $flags[] = 'length_edge_band';
            $notes[] = 'current_length_outside_preferred_preserve_band';
        }

        if ($this->aysa_technical_site_audit_has_truncation_signals($current)) {
            return $this->aysa_technical_site_audit_rewrite_decision('current_description_truncated_or_incomplete', 95, array_merge($flags, array('truncated')), array_merge($notes, array('current_description_looks_incomplete')));
        }
        if ($this->aysa_technical_site_audit_has_generic_filler($current)) {
            return $this->aysa_technical_site_audit_rewrite_decision('current_description_generic_or_filler', 92, array_merge($flags, array('generic_filler')), array_merge($notes, array('generic_filler_phrase_detected')));
        }
        if ($this->aysa_technical_site_audit_is_low_relevance_to_page_intent($current, $post)) {
            return $this->aysa_technical_site_audit_rewrite_decision('current_description_low_relevance_to_page_intent', 90, array_merge($flags, array('low_relevance')), array_merge($notes, array('weak_overlap_with_page_intent_terms')));
        }

        if ($primary_keyword === '') {
            $notes[] = $has_keyword_context ? 'primary_keyword_context_missing' : 'keyword_context_missing';
            return $this->aysa_technical_site_audit_manual_review_decision('keyword_context_missing_for_preserve_check', 58, array_merge($flags, array('missing_primary_context')), $notes);
        }

        if ($this->aysa_technical_site_audit_text_contains_keyword($current, $primary_keyword)) {
            $flags[] = 'primary_keyword_present';
        } else {
            return $this->aysa_technical_site_audit_rewrite_decision('primary_keyword_missing_from_current_description', 93, array_merge($flags, array('missing_primary_keyword')), array_merge($notes, array('primary_keyword_not_found_in_current_description')));
        }

        if (empty($secondary_keywords)) {
            return $this->aysa_technical_site_audit_manual_review_decision('secondary_keyword_context_missing_for_preserve_check', 56, array_merge($flags, array('missing_secondary_context')), array_merge($notes, array('secondary_keywords_not_available')));
        }

        $matched_secondary = $this->aysa_technical_site_audit_matched_secondary_keywords($current, $secondary_keywords);
        if (empty($matched_secondary)) {
            return $this->aysa_technical_site_audit_rewrite_decision('secondary_keyword_missing_from_current_description', 84, array_merge($flags, array('missing_secondary_keyword')), array_merge($notes, array('no_meaningful_secondary_keyword_match_found')));
        }
        $flags[] = 'secondary_keyword_present';
        $notes[] = 'matched_secondary_keywords:' . implode('|', array_slice($matched_secondary, 0, 3));

        if (!in_array('length_in_preserve_band', $flags, true)) {
            return $this->aysa_technical_site_audit_manual_review_decision('current_description_only_borderline_length', 64, $flags, $notes);
        }

        return array(
            'decision' => 'preserve',
            'overwrite_reason' => 'existing_meta_description_passes_quality_gate',
            'classification' => 'already_optimized',
            'quality_score' => 96,
            'inclusion_reason' => 'existing_meta_description_passes_quality_gate',
            'quality_flags' => $flags,
            'notes' => $notes,
        );
    }

    private function aysa_technical_site_audit_rewrite_decision($reason, $confidence, $flags, $notes) {
        return array(
            'decision' => 'rewrite',
            'overwrite_reason' => sanitize_key((string) $reason),
            'classification' => 'safe_auto_apply',
            'quality_score' => max(0, min(100, intval($confidence))),
            'inclusion_reason' => 'current_meta_description_failed_quality_gate',
            'quality_flags' => array_values(array_unique(array_map('sanitize_key', (array) $flags))),
            'notes' => array_values((array) $notes),
        );
    }

    private function aysa_technical_site_audit_manual_review_decision($reason, $confidence, $flags, $notes) {
        return array(
            'decision' => 'manual_review',
            'overwrite_reason' => sanitize_key((string) $reason),
            'classification' => 'manual_review_needed',
            'quality_score' => max(0, min(100, intval($confidence))),
            'inclusion_reason' => 'current_meta_description_requires_manual_review',
            'quality_flags' => array_values(array_unique(array_map('sanitize_key', (array) $flags))),
            'notes' => array_values((array) $notes),
        );
    }

    private function aysa_technical_site_audit_primary_keyword($post_id) {
        $post_id = intval($post_id);
        foreach (array('aysa_main_keyword', '_yoast_wpseo_focuskw', 'keyword', 'main_keyword', 'primary_keyword', 'aysa_keyword') as $meta_key) {
            $value = trim((string) get_post_meta($post_id, $meta_key, true));
            if ($value !== '') {
                return sanitize_text_field($value);
            }
        }
        return '';
    }

    private function aysa_technical_site_audit_secondary_keywords($post_id, $primary_keyword = '') {
        $post_id = intval($post_id);
        foreach (array('aysa_secondary_keywords', 'second_keyword', '_aysa_ai_secondary_keywords', 'secondary_keywords') as $meta_key) {
            $value = get_post_meta($post_id, $meta_key, true);
            $keywords = $this->aysa_technical_site_audit_keyword_list($value, (string) $primary_keyword);
            if (!empty($keywords)) {
                return $keywords;
            }
        }
        return array();
    }

    private function aysa_technical_site_audit_keyword_list($value, $primary_keyword = '') {
        $keywords = array();
        if (is_string($value)) {
            $parts = preg_split('/[,|;]/u', $value, -1, PREG_SPLIT_NO_EMPTY);
            $keywords = is_array($parts) ? $parts : array();
        } elseif (is_array($value)) {
            $keywords = array_map('strval', $value);
        }
        $primary_norm = $this->aysa_technical_site_audit_normalize_keyword((string) $primary_keyword);
        $out = array();
        foreach ($keywords as $keyword) {
            $keyword = trim((string) $keyword);
            if ($keyword === '') {
                continue;
            }
            if ($primary_norm !== '' && $this->aysa_technical_site_audit_normalize_keyword($keyword) === $primary_norm) {
                continue;
            }
            $out[] = sanitize_text_field($keyword);
        }
        return array_values(array_unique($out));
    }

    private function aysa_technical_site_audit_has_truncation_signals($value) {
        $trimmed = trim((string) $value);
        if ($trimmed === '') {
            return false;
        }
        if (preg_match('/(\.\.\.|…)\s*$/u', $trimmed)) {
            return true;
        }
        if (preg_match('/\b(si|sau|cu|de|din|pentru|la|in|în|pe|despre|catre|către)\s*$/iu', $trimmed)) {
            return true;
        }
        return preg_match('/[,;:\-]\s*$/u', $trimmed) === 1;
    }

    private function aysa_technical_site_audit_has_generic_filler($value) {
        $normalized = $this->aysa_technical_site_audit_normalize_text((string) $value);
        foreach (array('afla mai multe', 'descopera mai multe', 'click aici', 'intra acum', 'vezi mai multe', 'citeste mai mult', 'cele mai bune produse', 'produse de calitate', 'servicii de calitate', 'solutii complete', 'oferim servicii profesionale', 'alegerea perfecta') as $phrase) {
            if (strpos($normalized, $this->aysa_technical_site_audit_normalize_text($phrase)) !== false) {
                return true;
            }
        }
        return false;
    }

    private function aysa_technical_site_audit_is_low_relevance_to_page_intent($description, $post) {
        $intent_tokens = $this->aysa_technical_site_audit_page_intent_tokens($post);
        if (empty($intent_tokens)) {
            return false;
        }
        $description_tokens = $this->aysa_technical_site_audit_tokenize_text((string) $description);
        return count(array_intersect($intent_tokens, $description_tokens)) < 2;
    }

    private function aysa_technical_site_audit_page_intent_tokens($post) {
        $pool = array();
        foreach (array('post_title', 'title', 'permalink', 'url') as $key) {
            $pool = array_merge($pool, $this->aysa_technical_site_audit_tokenize_text((string) ($post->$key ?? '')));
        }
        $stopwords = array('si', 'sau', 'sunt', 'este', 'din', 'pentru', 'care', 'cele', 'this', 'with', 'from', 'your', 'the', 'and', 'www', 'https', 'http', 'com', 'ro');
        return array_values(array_unique(array_filter($pool, static function($token) use ($stopwords) {
            return mb_strlen((string) $token) >= 4 && !in_array($token, $stopwords, true);
        })));
    }

    private function aysa_technical_site_audit_text_contains_keyword($text, $keyword) {
        $normalized_text = $this->aysa_technical_site_audit_normalize_text((string) $text);
        $normalized_keyword = $this->aysa_technical_site_audit_normalize_keyword((string) $keyword);
        if ($normalized_keyword === '') {
            return false;
        }
        if (strpos($normalized_text, $normalized_keyword) !== false) {
            return true;
        }
        $keyword_tokens = $this->aysa_technical_site_audit_tokenize_text($normalized_keyword);
        if (count($keyword_tokens) <= 1) {
            return false;
        }
        $text_tokens = $this->aysa_technical_site_audit_tokenize_text($normalized_text);
        return count(array_intersect($keyword_tokens, $text_tokens)) >= max(2, count($keyword_tokens) - 1);
    }

    private function aysa_technical_site_audit_matched_secondary_keywords($text, $secondary_keywords) {
        $matched = array();
        foreach ((array) $secondary_keywords as $keyword) {
            $normalized_keyword = $this->aysa_technical_site_audit_normalize_keyword((string) $keyword);
            if ($normalized_keyword === '') {
                continue;
            }
            if (mb_strlen($normalized_keyword) >= 4 && strpos($this->aysa_technical_site_audit_normalize_text((string) $text), $normalized_keyword) !== false) {
                $matched[] = (string) $keyword;
                continue;
            }
            $keyword_tokens = $this->aysa_technical_site_audit_tokenize_text($normalized_keyword);
            if (!empty($keyword_tokens) && count(array_intersect($keyword_tokens, $this->aysa_technical_site_audit_tokenize_text((string) $text))) >= 1) {
                $matched[] = (string) $keyword;
            }
        }
        return array_values(array_unique($matched));
    }

    private function aysa_technical_site_audit_normalize_keyword($value) {
        return trim($this->aysa_technical_site_audit_normalize_text((string) $value));
    }

    private function aysa_technical_site_audit_normalize_text($value) {
        $value = mb_strtolower(trim((string) $value));
        $value = preg_replace('/[^\p{L}\p{N}\s-]+/u', ' ', $value);
        return trim(preg_replace('/\s+/u', ' ', (string) $value));
    }

    private function aysa_technical_site_audit_tokenize_text($value) {
        $parts = preg_split('/[\s\-\/]+/u', $this->aysa_technical_site_audit_normalize_text((string) $value), -1, PREG_SPLIT_NO_EMPTY);
        if (!is_array($parts)) {
            return array();
        }
        return array_values(array_unique(array_filter($parts, static function($token) {
            return mb_strlen((string) $token) >= 3;
        })));
    }

    private function aysa_technical_site_audit_trim_metadata_text($value, $max_length) {
        $value = trim(preg_replace('/\s+/', ' ', wp_strip_all_tags((string) $value)));
        $max_length = intval($max_length);
        if ($max_length <= 0 || mb_strlen($value) <= $max_length) {
            return sanitize_text_field($value);
        }
        $trimmed = mb_substr($value, 0, $max_length);
        $last_space = mb_strrpos($trimmed, ' ');
        if ($last_space !== false && $last_space > 40) {
            $trimmed = mb_substr($trimmed, 0, $last_space);
        }
        return sanitize_text_field(rtrim($trimmed, " \t\n\r\0\x0B,;-:"));
    }

    private function aysa_technical_site_audit_validate_metadata_proposal($field, $value) {
        $field = sanitize_key((string) $field);
        $value = trim(preg_replace('/\s+/', ' ', (string) $value));
        $length = mb_strlen($value);
        if ($value === '') {
            return array('ok' => false, 'reason' => 'empty_proposed_value');
        }
        if (preg_match('/[<>{}\[\]]/', $value)) {
            return array('ok' => false, 'reason' => 'unsafe_characters_in_proposed_value');
        }
        $tokens = preg_split('/\s+/u', mb_strtolower($value), -1, PREG_SPLIT_NO_EMPTY);
        $unique_tokens = array_unique(array_filter($tokens, static function($token) {
            return mb_strlen((string) $token) > 2;
        }));
        if ($field === 'meta_title' && ($length < 15 || $length > 70)) {
            return array('ok' => false, 'reason' => 'title_length_outside_safe_bounds');
        }
        if ($field === 'meta_title' && count($unique_tokens) < 3) {
            return array('ok' => false, 'reason' => 'title_too_generic');
        }
        if ($field === 'meta_description' && ($length < 70 || $length > 165)) {
            return array('ok' => false, 'reason' => 'description_length_outside_safe_bounds');
        }
        if ($field === 'meta_description' && count($unique_tokens) < 8) {
            return array('ok' => false, 'reason' => 'description_too_generic');
        }
        if ($field === 'meta_description' && preg_match('/\\s+(de|de a|pentru|pentru a|la|cu|si|și)$/iu', $value)) {
            return array('ok' => false, 'reason' => 'description_ends_with_incomplete_phrase');
        }
        if ($field === 'meta_description' && !preg_match('/[.!?]$/u', $value)) {
            return array('ok' => false, 'reason' => 'description_not_a_complete_sentence');
        }
        return array('ok' => true, 'reason' => '');
    }

    private function aysa_technical_site_audit_build_onpage_metadata_projection_row($project_id, $item, $post, $worker_run_id) {
        $project_id = intval($project_id);
        $post_id = intval($item['post_id'] ?? 0);
        $field = sanitize_key((string) ($item['field'] ?? ''));
        $proposed = sanitize_text_field((string) ($item['proposed_value'] ?? ''));
        $current_title = $this->aysa_technical_site_audit_current_post_meta_title($post_id, $post);
        $current_description = $this->aysa_technical_site_audit_current_post_meta_description($post_id, $post);
        $meta_title = $field === 'meta_title' ? $proposed : $current_title;
        $meta_description = $field === 'meta_description' ? $proposed : $current_description;

        return array(
            'entity_key' => 'post:' . $post_id,
            'entity_id' => $post_id,
            'object_kind' => 'post',
            'post_type' => sanitize_key((string) ($post->post_type ?? 'post')),
            'taxonomy' => '',
            'url' => esc_url_raw((string) ($item['url'] ?? get_permalink($post_id))),
            'title' => sanitize_text_field((string) ($post->post_title ?? '')),
            'entity_group' => sanitize_key((string) ($post->post_type ?? 'post')),
            'publication_state' => sanitize_key((string) ($post->post_status ?? 'publish')),
            'mapping_state' => 'mapped',
            'seo_state' => 'not_optimized',
            'optimization_scope' => 'meta_only',
            'meta_state' => 'generated',
            'rewrite_state' => 'not_needed',
            'output_state' => 'review_ready',
            'main_keyword' => '',
            'confidence_score' => 0.82,
            'decision_reason' => 'Prepared by Site Audit metadata remediation and applied through the On-Page metadata workspace.',
            'next_step' => 'Review generated meta',
            'content_source' => 'site_audit_metadata_remediation',
            'meta_package_json' => array(
                'meta_title' => $meta_title,
                'meta_description' => $meta_description,
                'confidence_score' => 0.82,
                'source' => 'site_audit_metadata_remediation',
                'site_audit_issue_type' => sanitize_key((string) ($item['issue_type'] ?? '')),
                'site_audit_issue_id' => sanitize_text_field((string) ($item['issue_id'] ?? '')),
                'target_field' => $field,
            ),
            'worker_run_id' => intval($worker_run_id),
            'last_evaluated_at' => current_time('mysql'),
        );
    }

    private function aysa_technical_site_audit_write_post_metadata($post_id, $field, $value) {
        $post_id = intval($post_id);
        $field = sanitize_key((string) $field);
        $value = sanitize_text_field((string) $value);
        if ($post_id <= 0 || $value === '') {
            return false;
        }
        if ($field === 'meta_title') {
            update_post_meta($post_id, '_aysa_seo_title', $value);
            update_post_meta($post_id, '_aysa_meta_title', $value);
            if (function_exists('is_plugin_active') && is_plugin_active('wordpress-seo/wp-seo.php')) {
                update_post_meta($post_id, '_yoast_wpseo_title', $value);
            }
            if (function_exists('is_plugin_active') && is_plugin_active('seo-by-rank-math/rank-math.php')) {
                update_post_meta($post_id, 'rank_math_title', $value);
            }
            return true;
        }
        if ($field === 'meta_description') {
            update_post_meta($post_id, '_aysa_seo_description', $value);
            update_post_meta($post_id, '_aysa_meta_description', $value);
            if (function_exists('is_plugin_active') && is_plugin_active('wordpress-seo/wp-seo.php')) {
                update_post_meta($post_id, '_yoast_wpseo_metadesc', $value);
            }
            if (function_exists('is_plugin_active') && is_plugin_active('seo-by-rank-math/rank-math.php')) {
                update_post_meta($post_id, 'rank_math_description', $value);
            }
            return true;
        }
        return false;
    }

    private function aysa_technical_site_audit_dispatch_metadata_remediation($project_id, $payload = array()) {
        $project_id = intval($project_id);
        $payload = is_array($payload) ? $payload : array();
        $snapshot = $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id);
        $bundle = $this->aysa_technical_site_audit_find_action_bundle($snapshot, 'prepare_metadata_remediation');
        if (empty($bundle)) {
            return array('applied' => false, 'reason' => 'metadata_bundle_not_available');
        }
        $package_id = sanitize_text_field((string) ($bundle['execution_package_id'] ?? ''));
        if ($package_id === '') {
            $package_id = 'site_audit_' . $project_id . '_prepare_metadata_remediation_' . substr(md5(current_time('mysql')), 0, 10);
        }
        $package = !empty($bundle['metadata_execution_package']) && is_array($bundle['metadata_execution_package'])
            ? $bundle['metadata_execution_package']
            : $this->aysa_technical_site_audit_build_metadata_execution_package($project_id, $snapshot, $bundle, $package_id);
        $result = array(
            'result_type' => 'worker_dispatched',
            'worker_run_id' => intval($payload['worker_run_id'] ?? 0),
            'worker_task_id' => intval($payload['worker_task_id'] ?? 0),
            'automatic_site_mutation' => true,
            'message' => 'Metadata remediation was queued for the worker. Supported WordPress post metadata will be updated by the worker callback.',
            'package_items_count' => intval($package['items_count'] ?? 0),
            'package_skipped_count' => intval($package['skipped_count'] ?? 0),
        );
        $communication = $this->aysa_agent8_create_site_audit_communication_event(
            $project_id,
            'site_audit_metadata_apply_started',
            'Agent8 Site Audit: ' . $this->aysa_technical_site_audit_action_subject_label('prepare_metadata_remediation', $package) . ' started',
            sprintf(
                "Relax, I got this.\n\nI sent the Site Audit metadata package to the worker. I will apply only clearly mapped WordPress pages through the shared On-Page metadata path.\n\nScope: %d ready items, %d skipped before apply.",
                intval($package['items_count'] ?? 0),
                intval($package['skipped_count'] ?? 0)
            ),
            array(
                'action_key' => 'prepare_metadata_remediation',
                'worker_run_id' => intval($payload['worker_run_id'] ?? 0),
                'worker_task_id' => intval($payload['worker_task_id'] ?? 0),
                'package_id' => $package_id,
            )
        );
        $result['communication_event'] = $communication;
        $this->aysa_adjustments_update_site_audit_action_bundle_state($project_id, 'prepare_metadata_remediation', array(
            'approval_state' => 'applying',
            'execution_state' => 'applying',
            'execution_package_id' => $package_id,
            'metadata_execution_package' => $package,
            'last_apply_result' => $result,
            'worker_run_id' => intval($payload['worker_run_id'] ?? 0),
            'worker_task_id' => intval($payload['worker_task_id'] ?? 0),
            'last_error' => '',
        ));
        return array_merge(array(
            'applied' => true,
            'stage' => 'apply',
            'execution_state' => 'applying',
            'action_key' => 'prepare_metadata_remediation',
            'execution_package_id' => $package_id,
        ), $result);
    }

    private function aysa_technical_site_audit_dispatch_image_alt_generation($project_id, $payload = array()) {
        $project_id = intval($project_id);
        $payload = is_array($payload) ? $payload : array();
        $snapshot = $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id);
        $bundle = $this->aysa_technical_site_audit_find_action_bundle($snapshot, 'prepare_image_alt_text');
        $package = isset($payload['image_alt_execution_package']) && is_array($payload['image_alt_execution_package'])
            ? $payload['image_alt_execution_package']
            : array();
        if (empty($bundle) && empty($package)) {
            return array('applied' => false, 'reason' => 'image_alt_bundle_not_available');
        }
        $package_id = sanitize_text_field((string) ($bundle['execution_package_id'] ?? ($package['package_id'] ?? '')));
        if ($package_id === '') {
            $package_id = 'site_audit_' . $project_id . '_prepare_image_alt_text_' . substr(md5(current_time('mysql')), 0, 10);
        }
        $result = array(
            'result_type' => 'worker_dispatched',
            'worker_run_id' => intval($payload['worker_run_id'] ?? 0),
            'worker_task_id' => intval($payload['worker_task_id'] ?? 0),
            'automatic_site_mutation' => false,
            'message' => 'The worker is analyzing mapped images and will return a reviewed image alt package before anything is applied.',
            'seed_candidate_count' => intval($package['seed_candidate_count'] ?? count((array) ($package['candidates'] ?? array()))),
        );
        $result['communication_event'] = $this->aysa_agent8_create_site_audit_communication_event(
            $project_id,
            'site_audit_image_alt_generation_started',
            'Agent8 Site Audit: Image alt text started',
            "Relax, I got this. I’m analyzing your images.",
            array(
                'action_key' => 'prepare_image_alt_text',
                'worker_run_id' => intval($payload['worker_run_id'] ?? 0),
                'worker_task_id' => intval($payload['worker_task_id'] ?? 0),
                'package_id' => $package_id,
            ),
            'info'
        );
        $this->aysa_adjustments_update_site_audit_action_bundle_state($project_id, 'prepare_image_alt_text', array(
            'approval_state' => 'preparing',
            'execution_state' => 'preparing',
            'execution_package_id' => $package_id,
            'image_alt_execution_package' => $package,
            'last_prepare_result' => $result,
            'last_error' => '',
            'worker_run_id' => intval($payload['worker_run_id'] ?? 0),
            'worker_task_id' => intval($payload['worker_task_id'] ?? 0),
        ));
        return array_merge(array(
            'applied' => true,
            'stage' => 'prepare',
            'execution_state' => 'preparing',
            'action_key' => 'prepare_image_alt_text',
            'execution_package_id' => $package_id,
        ), $result);
    }

    private function aysa_technical_site_audit_dispatch_shared_action($project_id, $payload = array()) {
        $project_id = intval($project_id);
        $payload = is_array($payload) ? $payload : array();
        $action_key = sanitize_key((string) ($payload['action_key'] ?? ''));
        $operation = sanitize_key((string) ($payload['executor_operation'] ?? ''));
        if ($project_id <= 0 || $action_key === '' || $operation === '') {
            return array('applied' => false, 'reason' => 'shared_action_dispatch_context_required');
        }

        $snapshot = $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id);
        $bundle = $this->aysa_technical_site_audit_find_action_bundle($snapshot, $action_key);
        if (empty($bundle)) {
            return array('applied' => false, 'reason' => 'action_bundle_not_available');
        }

        $package_id = sanitize_text_field((string) ($bundle['execution_package_id'] ?? ''));
        if ($package_id === '') {
            $package_id = 'site_audit_' . $project_id . '_' . $action_key . '_' . substr(md5(current_time('mysql')), 0, 10);
        }
        $maturity = $this->aysa_technical_site_audit_action_maturity($action_key);
        $maturity_label = $this->aysa_technical_site_audit_action_maturity_label($maturity);
        $redirect_package = array();
        $canonical_package = array();
        $internal_linking_package = array();
        $image_alt_package = array();
        if ($action_key === 'prepare_response_code_review') {
            $redirect_package = isset($payload['redirect_execution_package']) && is_array($payload['redirect_execution_package'])
                ? $payload['redirect_execution_package']
                : $this->aysa_technical_site_audit_get_redirect_execution_package($project_id, $snapshot, $bundle, $package_id);
            $bundle['redirect_execution_package'] = $redirect_package;
        } elseif ($action_key === 'prepare_indexability_review') {
            $canonical_package = isset($payload['canonical_execution_package']) && is_array($payload['canonical_execution_package']) && !empty($payload['canonical_execution_package'])
                ? $payload['canonical_execution_package']
                : (isset($bundle['canonical_execution_package']) && is_array($bundle['canonical_execution_package']) ? $bundle['canonical_execution_package'] : array());
            $bundle['canonical_execution_package'] = $canonical_package;
        } elseif ($action_key === 'prepare_internal_linking_plan') {
            $internal_linking_package = isset($payload['internal_linking_execution_package']) && is_array($payload['internal_linking_execution_package'])
                ? $payload['internal_linking_execution_package']
                : array();
            $bundle['internal_linking_execution_package'] = $internal_linking_package;
        } elseif ($action_key === 'prepare_image_alt_text') {
            $image_alt_package = isset($payload['image_alt_execution_package']) && is_array($payload['image_alt_execution_package']) && !empty($payload['image_alt_execution_package'])
                ? $payload['image_alt_execution_package']
                : (isset($bundle['image_alt_execution_package']) && is_array($bundle['image_alt_execution_package']) ? $bundle['image_alt_execution_package'] : array());
            $bundle['image_alt_execution_package'] = $image_alt_package;
        }
        $result = array(
            'result_type' => 'worker_dispatched',
            'worker_run_id' => intval($payload['worker_run_id'] ?? 0),
            'worker_task_id' => intval($payload['worker_task_id'] ?? 0),
            'worker_task_group' => sanitize_key((string) ($payload['worker_task_group'] ?? '')),
            'executor_operation' => $operation,
            'execution_maturity' => $maturity,
            'execution_maturity_label' => $maturity_label,
            'automatic_site_mutation' => in_array($maturity, array('real_executor', 'shared_executor_with_limitations'), true),
            'message' => $maturity === 'adapter_manual_needed_only'
                ? 'This Site Audit package was sent to a worker-controlled adapter for manual-needed classification, not automatic remediation.'
                : 'This Site Audit package was sent to the mapped worker execution path.',
        );
        if (!empty($redirect_package)) {
            $result['redirect_execution_package'] = array(
                'package_id' => sanitize_text_field((string) ($redirect_package['package_id'] ?? $package_id)),
                'total_candidates' => intval($redirect_package['total_candidates'] ?? 0),
                'safe_auto_apply_count' => intval($redirect_package['safe_auto_apply_count'] ?? 0),
                'manual_review_needed_count' => intval($redirect_package['manual_review_needed_count'] ?? 0),
                'no_valid_target_count' => intval($redirect_package['no_valid_target_count'] ?? 0),
            );
        }
        if (!empty($canonical_package)) {
            $result['canonical_execution_package'] = array(
                'package_id' => sanitize_text_field((string) ($canonical_package['package_id'] ?? $package_id)),
                'total_candidates' => intval($canonical_package['total_candidates'] ?? 0),
                'self_canonical_count' => intval($canonical_package['self_canonical_count'] ?? 0),
                'rewrite_count' => intval($canonical_package['rewrite_count'] ?? 0),
                'manual_review_needed_count' => intval($canonical_package['manual_review_needed_count'] ?? 0),
            );
        }
        if (!empty($internal_linking_package)) {
            $result['internal_linking_execution_package'] = array(
                'package_id' => sanitize_text_field((string) ($internal_linking_package['package_id'] ?? $package_id)),
                'total_candidates' => intval($internal_linking_package['total_candidates'] ?? 0),
                'safe_auto_apply_count' => intval($internal_linking_package['safe_auto_apply_count'] ?? 0),
                'manual_review_needed_count' => intval($internal_linking_package['manual_review_needed_count'] ?? 0),
                'no_valid_source_count' => intval($internal_linking_package['no_valid_source_count'] ?? 0),
            );
        }
        if (!empty($image_alt_package)) {
            $result['image_alt_execution_package'] = array(
                'package_id' => sanitize_text_field((string) ($image_alt_package['package_id'] ?? $package_id)),
                'total_candidates' => intval($image_alt_package['total_candidates'] ?? 0),
                'safe_auto_apply_count' => intval($image_alt_package['safe_auto_apply_count'] ?? 0),
                'manual_review_needed_count' => intval($image_alt_package['manual_review_needed_count'] ?? 0),
                'decorative_ignore_count' => intval($image_alt_package['decorative_ignore_count'] ?? 0),
                'preserve_count' => intval($image_alt_package['preserve_count'] ?? 0),
            );
        }
        $external_event = isset($payload['communication_event']) && is_array($payload['communication_event']) ? $payload['communication_event'] : array();
        if (!empty($external_event)) {
            $external_event['payload'] = array_merge(isset($external_event['payload']) && is_array($external_event['payload']) ? $external_event['payload'] : array(), array(
                'action_key' => $action_key,
                'executor_operation' => $operation,
                'execution_maturity' => $maturity,
                'execution_maturity_label' => $maturity_label,
                'worker_run_id' => intval($payload['worker_run_id'] ?? 0),
                'worker_task_id' => intval($payload['worker_task_id'] ?? 0),
                'package_id' => $package_id,
            ));
            $result['communication_event'] = $this->aysa_technical_site_audit_create_external_communication_event($project_id, $external_event);
        } else {
            $result['communication_event'] = $this->aysa_agent8_create_site_audit_communication_event(
                $project_id,
                'site_audit_shared_action_started',
                'Agent8 Site Audit: ' . $this->aysa_technical_site_audit_action_subject_label($action_key) . ' started',
                $this->aysa_technical_site_audit_start_communication_message($action_key, $operation, $bundle, $maturity_label, $maturity),
                array(
                    'action_key' => $action_key,
                    'executor_operation' => $operation,
                    'execution_maturity' => $maturity,
                    'execution_maturity_label' => $maturity_label,
                    'worker_run_id' => intval($payload['worker_run_id'] ?? 0),
                    'worker_task_id' => intval($payload['worker_task_id'] ?? 0),
                    'package_id' => $package_id,
                )
            );
        }
        $state_changes = array(
            'approval_state' => 'applying',
            'execution_state' => 'applying',
            'execution_package_id' => $package_id,
            'last_apply_result' => $result,
            'worker_run_id' => intval($payload['worker_run_id'] ?? 0),
            'worker_task_id' => intval($payload['worker_task_id'] ?? 0),
            'last_error' => '',
        );
        if (!empty($canonical_package)) {
            $state_changes['canonical_execution_package'] = $canonical_package;
            $state_changes['last_prepare_result'] = array(
                'result_type' => 'canonical_execution_package_prepared',
                'canonical_execution_package' => $canonical_package,
                'total_candidates' => intval($canonical_package['total_candidates'] ?? 0),
                'self_canonical_count' => intval($canonical_package['self_canonical_count'] ?? 0),
                'rewrite_count' => intval($canonical_package['rewrite_count'] ?? 0),
                'manual_review_needed_count' => intval($canonical_package['manual_review_needed_count'] ?? 0),
                'automatic_site_mutation' => false,
            );
        }
        if (!empty($redirect_package)) {
            $state_changes['last_prepare_result'] = array(
                'result_type' => 'redirect_execution_package_prepared',
                'redirect_execution_package' => $redirect_package,
                'total_candidates' => intval($redirect_package['total_candidates'] ?? 0),
                'safe_auto_apply_count' => intval($redirect_package['safe_auto_apply_count'] ?? 0),
                'manual_review_needed_count' => intval($redirect_package['manual_review_needed_count'] ?? 0),
                'no_valid_target_count' => intval($redirect_package['no_valid_target_count'] ?? 0),
                'automatic_site_mutation' => false,
            );
        }
        if (!empty($internal_linking_package)) {
            $state_changes['last_prepare_result'] = array(
                'result_type' => 'internal_linking_execution_package_prepared',
                'internal_linking_execution_package' => $internal_linking_package,
                'total_candidates' => intval($internal_linking_package['total_candidates'] ?? 0),
                'safe_auto_apply_count' => intval($internal_linking_package['safe_auto_apply_count'] ?? 0),
                'manual_review_needed_count' => intval($internal_linking_package['manual_review_needed_count'] ?? 0),
                'no_valid_source_count' => intval($internal_linking_package['no_valid_source_count'] ?? 0),
                'automatic_site_mutation' => false,
            );
        }
        if (!empty($image_alt_package)) {
            $state_changes['image_alt_execution_package'] = $image_alt_package;
            $state_changes['last_prepare_result'] = array(
                'result_type' => 'image_alt_execution_package_prepared',
                'image_alt_execution_package' => $image_alt_package,
                'total_candidates' => intval($image_alt_package['total_candidates'] ?? 0),
                'safe_auto_apply_count' => intval($image_alt_package['safe_auto_apply_count'] ?? 0),
                'manual_review_needed_count' => intval($image_alt_package['manual_review_needed_count'] ?? 0),
                'decorative_ignore_count' => intval($image_alt_package['decorative_ignore_count'] ?? 0),
                'preserve_count' => intval($image_alt_package['preserve_count'] ?? 0),
                'automatic_site_mutation' => false,
            );
        }
        $this->aysa_adjustments_update_site_audit_action_bundle_state($project_id, $action_key, $state_changes);

        return array_merge(array(
            'applied' => true,
            'stage' => 'apply',
            'execution_state' => 'applying',
            'action_key' => $action_key,
            'execution_package_id' => $package_id,
        ), $result);
    }

    private function aysa_technical_site_audit_action_maturity($action_key) {
        $action_key = sanitize_key((string) $action_key);
        if ($action_key === 'prepare_metadata_remediation') {
            return 'real_executor';
        }
        if (in_array($action_key, array('prepare_response_code_review', 'prepare_internal_linking_plan'), true)) {
            return 'shared_executor_with_limitations';
        }
        if ($action_key === 'prepare_image_alt_text') {
            return 'shared_executor_with_limitations';
        }
        if ($action_key === 'prepare_indexability_review') {
            return 'shared_executor_with_limitations';
        }
        return 'not_really_implemented';
    }

    private function aysa_technical_site_audit_action_maturity_label($maturity) {
        $labels = array(
            'real_executor' => 'REAL EXECUTOR',
            'shared_executor_with_limitations' => 'SHARED EXECUTOR WITH LIMITATIONS',
            'adapter_manual_needed_only' => 'ADAPTER / MANUAL-NEEDED ONLY',
            'not_really_implemented' => 'NOT REALLY IMPLEMENTED',
        );
        return $labels[$maturity] ?? 'NOT REALLY IMPLEMENTED';
    }

    private function aysa_technical_site_audit_action_subject_label($action_key, $package = array()) {
        $action_key = sanitize_key((string) $action_key);
        if ($action_key === 'prepare_metadata_remediation') {
            $fields = array();
            foreach ((array) ($package['items'] ?? array()) as $item) {
                if (is_array($item)) {
                    $field = sanitize_key((string) ($item['field'] ?? ''));
                    if ($field !== '') {
                        $fields[$field] = true;
                    }
                }
            }
            if (isset($fields['meta_title']) && !isset($fields['meta_description'])) {
                return 'Page titles';
            }
            if (isset($fields['meta_description']) && !isset($fields['meta_title'])) {
                return 'Meta descriptions';
            }
            return 'Page titles & meta descriptions';
        }
        $labels = array(
            'prepare_response_code_review' => 'Response codes & redirects',
            'prepare_indexability_review' => 'Canonical & noindex',
            'prepare_internal_linking_plan' => 'Internal linking',
            'prepare_image_alt_text' => 'Image alt text & media',
        );
        return $labels[$action_key] ?? 'Site Audit action';
    }

    private function aysa_technical_site_audit_issue_family($action_key) {
        $map = array(
            'prepare_metadata_remediation' => 'metadata',
            'prepare_response_code_review' => 'response_codes_redirects',
            'prepare_internal_linking_plan' => 'internal_linking',
        );
        $action_key = sanitize_key((string) $action_key);
        return $map[$action_key] ?? sanitize_key((string) str_replace('prepare_', '', $action_key));
    }

    private function aysa_technical_site_audit_normalize_apply_result($action_key, $package_id, $result) {
        $result = is_array($result) ? $result : array();
        $rows = isset($result['results']) && is_array($result['results']) ? array_values($result['results']) : array();
        $already_present = 0;
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $status = sanitize_key((string) ($row['status'] ?? ''));
            $reason = sanitize_key((string) ($row['reason'] ?? ''));
            if ($status === 'already_present' || $reason === 'already_matches_proposal' || $reason === 'anchor_not_found_or_already_linked') {
                $already_present++;
            }
        }
        $executor_path = sanitize_text_field((string) ($result['executor_path'] ?? $result['shared_executor'] ?? $result['shared_metadata_path'] ?? ''));
        $manual_needed = intval($result['manual_needed_count'] ?? $result['manual_review_needed_count'] ?? 0);
        $normalized = array(
            'issue_family' => $this->aysa_technical_site_audit_issue_family($action_key),
            'package_id' => sanitize_text_field((string) $package_id),
            'execution_package_id' => sanitize_text_field((string) ($result['execution_package_id'] ?? $package_id)),
            'executor_path' => $executor_path,
            'applied_count' => intval($result['applied_count'] ?? 0),
            'skipped_count' => intval($result['skipped_count'] ?? 0),
            'failed_count' => intval($result['failed_count'] ?? 0),
            'manual_needed_count' => $manual_needed,
            'already_present_count' => $already_present,
            'verification_ready' => (intval($result['applied_count'] ?? 0) + $already_present) > 0,
            'per_item_results' => $rows,
            'row_results' => $rows,
        );
        return array_merge($result, $normalized);
    }

    private function aysa_technical_site_audit_normalize_verification_result($verify_result) {
        $verify_result = is_array($verify_result) ? $verify_result : array();
        $checked = intval($verify_result['checked_count'] ?? 0);
        $verified = intval($verify_result['verified_count'] ?? 0);
        $pending = max(0, $checked - $verified);
        return array_merge($verify_result, array(
            'verification_pending_count' => intval($verify_result['verification_pending_count'] ?? $pending),
            'failed_verification_count' => intval($verify_result['failed_verification_count'] ?? $pending),
            'evidence_summary' => sanitize_text_field((string) ($verify_result['evidence_summary'] ?? $verify_result['evidence'] ?? '')),
        ));
    }

    private function aysa_technical_site_audit_start_communication_message($action_key, $operation, $bundle, $maturity_label, $maturity) {
        $affected = intval($bundle['affected_count'] ?? 0);
        if ($maturity === 'adapter_manual_needed_only') {
            return sprintf(
                "I am checking this Site Audit package through a worker-controlled adapter.\n\nAction: %s\nMaturity: %s\nAffected items: %d\n\nThis is not a mature automatic executor yet. I will not present these items as fixed unless a supported shared executor actually changes them.",
                $action_key,
                $maturity_label,
                $affected
            );
        }
        if ($maturity === 'shared_executor_with_limitations') {
            $redirect_package = isset($bundle['redirect_execution_package']) && is_array($bundle['redirect_execution_package']) ? $bundle['redirect_execution_package'] : array();
            $internal_linking_package = isset($bundle['internal_linking_execution_package']) && is_array($bundle['internal_linking_execution_package']) ? $bundle['internal_linking_execution_package'] : array();
            $redirect_counts = '';
            if (!empty($redirect_package) && sanitize_key((string) ($bundle['action_key'] ?? '')) === 'prepare_response_code_review') {
                $redirect_counts = sprintf(
                    "\nRedirect candidates: %d total, %d safe auto-apply, %d manual review, %d without valid target.",
                    intval($redirect_package['total_candidates'] ?? 0),
                    intval($redirect_package['safe_auto_apply_count'] ?? 0),
                    intval($redirect_package['manual_review_needed_count'] ?? 0),
                    intval($redirect_package['no_valid_target_count'] ?? 0)
                );
            }
            if (!empty($internal_linking_package) && sanitize_key((string) ($bundle['action_key'] ?? '')) === 'prepare_internal_linking_plan') {
                $redirect_counts = sprintf(
                    "\nInternal linking candidates: %d total, %d safe auto-apply, %d manual review, %d without valid source.",
                    intval($internal_linking_package['total_candidates'] ?? 0),
                    intval($internal_linking_package['safe_auto_apply_count'] ?? 0),
                    intval($internal_linking_package['manual_review_needed_count'] ?? 0),
                    intval($internal_linking_package['no_valid_source_count'] ?? 0)
                );
            }
            return sprintf(
                "I am starting this Site Audit package through the mapped shared execution path.\n\nAction: %s\nExecutor: %s\nMaturity: %s\nAffected items: %d%s\n\nI will apply only rows that are safely supported by the shared path. Unsupported rows stay skipped or manual-needed.",
                $action_key,
                $operation,
                $maturity_label,
                $affected,
                $redirect_counts
            );
        }
        return sprintf(
            "I am starting this Site Audit package through the mature shared executor.\n\nAction: %s\nExecutor: %s\nMaturity: %s\nAffected items: %d.",
            $action_key,
            $operation,
            $maturity_label,
            $affected
        );
    }

    private function aysa_technical_site_audit_dispatch_canonical_generation($project_id, $payload = array()) {
        $project_id = intval($project_id);
        $payload = is_array($payload) ? $payload : array();
        $snapshot = $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id);
        $bundle = $this->aysa_technical_site_audit_find_action_bundle($snapshot, 'prepare_indexability_review');
        $package = isset($payload['canonical_execution_package']) && is_array($payload['canonical_execution_package'])
            ? $payload['canonical_execution_package']
            : array();
        $package_id = sanitize_text_field((string) ($package['package_id'] ?? ($bundle['execution_package_id'] ?? '')));
        if ($package_id === '') {
            $package_id = 'site_audit_' . $project_id . '_prepare_indexability_review_' . substr(md5(current_time('mysql')), 0, 10);
        }
        $this->aysa_agent8_create_site_audit_communication_event(
            $project_id,
            'site_audit_canonical_prepare_started',
            'Agent8 Site Audit: Canonical remediation started',
            "Relax, I got this. I’m analyzing canonical issues.",
            array(
                'action_key' => 'prepare_indexability_review',
                'package_id' => $package_id,
                'worker_run_id' => intval($payload['worker_run_id'] ?? 0),
                'worker_task_id' => intval($payload['worker_task_id'] ?? 0),
            ),
            'info'
        );
        $this->aysa_adjustments_update_site_audit_action_bundle_state($project_id, 'prepare_indexability_review', array(
            'approval_state' => 'preparing',
            'execution_state' => 'preparing',
            'execution_package_id' => $package_id,
            'canonical_execution_package' => $package,
            'last_error' => '',
        ));
        return array(
            'applied' => true,
            'stage' => 'prepare',
            'execution_state' => 'preparing',
            'action_key' => 'prepare_indexability_review',
            'execution_package_id' => $package_id,
        );
    }

    private function aysa_technical_site_audit_completion_communication_message($action_key, $maturity_label, $maturity, $applied_count, $skipped_count, $manual_needed_count, $failed_count, $result = array()) {
        $result = is_array($result) ? $result : array();
        $redirect_counts = '';
        if ($action_key === 'prepare_response_code_review') {
            $redirect_counts = sprintf(
                "\nRedirect candidates: %d total, %d safe auto-apply, %d manual review, %d without valid target.",
                intval($result['total_candidates'] ?? 0),
                intval($result['safe_auto_apply_count'] ?? 0),
                intval($result['manual_review_needed_count'] ?? 0),
                intval($result['no_valid_target_count'] ?? 0)
            );
        } elseif ($action_key === 'prepare_internal_linking_plan') {
            $redirect_counts = sprintf(
                "\nInternal linking candidates: %d total, %d safe auto-apply, %d manual review, %d without valid source.",
                intval($result['total_candidates'] ?? 0),
                intval($result['safe_auto_apply_count'] ?? 0),
                intval($result['manual_review_needed_count'] ?? 0),
                intval($result['no_valid_source_count'] ?? 0)
            );
        }
        if ($maturity === 'adapter_manual_needed_only' || $manual_needed_count > 0 || $applied_count <= 0) {
            return sprintf(
                "I checked the Site Audit package, but I did not treat it as automatic remediation.\n\nAction: %s\nMaturity: %s\nApplied: %d\nSkipped: %d\nManual review needed: %d\nFailed: %d%s\n\nNext: these items need manual review or a mature shared executor before they can be presented as fixed.",
                $action_key,
                $maturity_label,
                $applied_count,
                $skipped_count,
                $manual_needed_count,
                $failed_count,
                $redirect_counts
            );
        }
        return sprintf(
            "I finished the Site Audit package through the supported execution path.\n\nAction: %s\nMaturity: %s\nApplied: %d\nSkipped: %d\nManual review needed: %d\nFailed: %d%s\n\nNext: use Verify by audit delta only after there is evidence from a newer crawl or live readback.",
            $action_key,
            $maturity_label,
            $applied_count,
            $skipped_count,
            $manual_needed_count,
            $failed_count,
            $redirect_counts
        );
    }

    private function aysa_technical_site_audit_apply_shared_action($project_id, $payload = array()) {
        $project_id = intval($project_id);
        $payload = is_array($payload) ? $payload : array();
        $operation = sanitize_key((string) ($payload['operation'] ?? ''));
        $operation_map = array(
            'site_audit_redirects_apply' => 'prepare_response_code_review',
            'site_audit_canonical_apply' => 'prepare_indexability_review',
            'site_audit_internal_linking_apply' => 'prepare_internal_linking_plan',
            'site_audit_image_alt_apply' => 'prepare_image_alt_text',
        );
        $action_key = sanitize_key((string) ($payload['action_key'] ?? ($operation_map[$operation] ?? '')));
        if ($project_id <= 0 || $operation === '' || $action_key === '') {
            return array('applied' => false, 'reason' => 'shared_action_apply_context_required');
        }

        $snapshot = $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id);
        $bundle = $this->aysa_technical_site_audit_find_action_bundle($snapshot, $action_key);
        if (empty($bundle)) {
            return array('applied' => false, 'reason' => 'action_bundle_not_available');
        }
        $package_id = sanitize_text_field((string) ($bundle['execution_package_id'] ?? ''));
        if ($package_id === '') {
            $package_id = 'site_audit_' . $project_id . '_' . $action_key . '_' . substr(md5(current_time('mysql')), 0, 10);
        }
        $maturity = $this->aysa_technical_site_audit_action_maturity($action_key);
        $maturity_label = $this->aysa_technical_site_audit_action_maturity_label($maturity);

        $rows = $this->aysa_technical_site_audit_collect_bundle_rows($snapshot, $bundle);
        if ($operation === 'site_audit_redirects_apply') {
            $redirect_package = isset($payload['redirect_execution_package']) && is_array($payload['redirect_execution_package'])
                ? $payload['redirect_execution_package']
                : $this->aysa_technical_site_audit_get_redirect_execution_package($project_id, $snapshot, $bundle, $package_id);
            $result = $this->aysa_technical_site_audit_apply_redirect_package($project_id, $redirect_package);
        } elseif ($operation === 'site_audit_canonical_apply') {
            $canonical_package = isset($payload['canonical_execution_package']) && is_array($payload['canonical_execution_package'])
                ? $payload['canonical_execution_package']
                : $this->aysa_technical_site_audit_get_canonical_execution_package($bundle);
            $result = $this->aysa_technical_site_audit_apply_canonical_package($project_id, $canonical_package);
        } elseif ($operation === 'site_audit_internal_linking_apply') {
            $internal_linking_package = isset($payload['internal_linking_execution_package']) && is_array($payload['internal_linking_execution_package'])
                ? $payload['internal_linking_execution_package']
                : $this->aysa_technical_site_audit_get_internal_linking_execution_package($bundle);
            $result = $this->aysa_technical_site_audit_apply_internal_linking_package($project_id, $internal_linking_package);
        } elseif ($operation === 'site_audit_image_alt_apply') {
            $image_alt_package = isset($payload['image_alt_execution_package']) && is_array($payload['image_alt_execution_package']) && !empty($payload['image_alt_execution_package'])
                ? $payload['image_alt_execution_package']
                : $this->aysa_technical_site_audit_get_image_alt_execution_package($bundle);
            $result = $this->aysa_technical_site_audit_apply_image_alt_package($project_id, $image_alt_package);
        } else {
            return array('applied' => false, 'reason' => 'unsupported_shared_action_operation');
        }

        $applied_count = intval($result['applied_count'] ?? 0);
        $failed_count = intval($result['failed_count'] ?? 0);
        $manual_needed_count = intval($result['manual_needed_count'] ?? 0);
        $skipped_count = intval($result['skipped_count'] ?? 0);
        $state = $failed_count > 0 && $applied_count <= 0 && $manual_needed_count <= 0 ? 'failed' : ($applied_count > 0 ? 'applied' : 'manual_needed');
        $result = array_merge($result, array(
            'result_type' => sanitize_key($operation . '_worker_result'),
            'worker_run_id' => intval($payload['worker_run_id'] ?? 0),
            'worker_task_id' => intval($payload['worker_task_id'] ?? 0),
            'worker_task_group' => sanitize_key((string) ($payload['worker_task_group'] ?? '')),
            'executor_operation' => $operation,
            'execution_maturity' => $maturity,
            'execution_maturity_label' => $maturity_label,
            'automatic_site_mutation' => in_array($maturity, array('real_executor', 'shared_executor_with_limitations'), true) && $applied_count > 0,
            'execution_package_id' => $package_id,
        ));
        $result = $this->aysa_technical_site_audit_normalize_apply_result($action_key, $package_id, $result);
        if (empty($payload['suppress_plugin_communication'])) {
            $result['communication_event'] = $this->aysa_agent8_create_site_audit_communication_event(
                $project_id,
                $state === 'failed' ? 'site_audit_shared_action_failed' : 'site_audit_shared_action_completed',
                'Agent8 Site Audit: ' . $this->aysa_technical_site_audit_action_subject_label($action_key) . ($state === 'failed' ? ' failed' : ($applied_count > 0 ? ' completed' : ' needs review')),
                $this->aysa_technical_site_audit_completion_communication_message($action_key, $maturity_label, $maturity, $applied_count, $skipped_count, $manual_needed_count, $failed_count, $result),
                array(
                    'action_key' => $action_key,
                    'executor_operation' => $operation,
                    'execution_maturity' => $maturity,
                    'execution_maturity_label' => $maturity_label,
                    'package_id' => $package_id,
                    'applied_count' => $applied_count,
                    'skipped_count' => $skipped_count,
                    'manual_needed_count' => $manual_needed_count,
                    'failed_count' => $failed_count,
                ),
                $state === 'failed' || $manual_needed_count > 0 ? 'warning' : 'info'
            );
        } else {
            $result['communication_event'] = array('ok' => true, 'source' => 'suppressed_for_worker_shared_service');
        }
        $this->aysa_adjustments_update_site_audit_action_bundle_state($project_id, $action_key, array(
            'approval_state' => $state,
            'execution_state' => $state,
            'execution_package_id' => $package_id,
            'last_applied_at' => current_time('mysql'),
            'last_apply_result' => $result,
            'last_error' => $state === 'failed' ? sanitize_text_field((string) ($result['reason'] ?? 'site_audit_shared_action_failed')) : '',
        ));

        return array(
            'applied' => true,
            'stage' => 'apply',
            'execution_state' => $state,
            'action_key' => $action_key,
            'execution_package_id' => $package_id,
            'execution_maturity' => $maturity,
            'execution_maturity_label' => $maturity_label,
            'applied_count' => $applied_count,
            'skipped_count' => $skipped_count,
            'manual_needed_count' => $manual_needed_count,
            'failed_count' => $failed_count,
            'apply_result' => $result,
        );
    }

    private function aysa_technical_site_audit_collect_bundle_rows($snapshot, $bundle) {
        $categories = array();
        foreach ((array) ($bundle['issue_breakdown'] ?? array()) as $issue) {
            if (is_array($issue)) {
                $category = sanitize_key((string) ($issue['category'] ?? ''));
                if ($category !== '') {
                    $categories[$category] = true;
                }
            }
        }
        if (empty($categories)) {
            $category = sanitize_key((string) ($bundle['issue_category'] ?? ''));
            if ($category !== '') {
                $categories[$category] = true;
            }
        }
        $action_key = sanitize_key((string) ($bundle['action_key'] ?? ''));
        $fallback = array(
            'prepare_response_code_review' => array('response_codes', 'server'),
            'prepare_indexability_review' => array('canonical', 'indexability'),
            'prepare_internal_linking_plan' => array('internal_linking'),
            'prepare_image_alt_text' => array('media'),
        );
        foreach ((array) ($fallback[$action_key] ?? array()) as $category) {
            $categories[sanitize_key((string) $category)] = true;
        }
        $rows = array();
        foreach ((array) ($snapshot['rows'] ?? array()) as $issue) {
            if (!is_array($issue) || !isset($categories[sanitize_key((string) ($issue['category'] ?? ''))])) {
                continue;
            }
            $affected = isset($issue['affected_page_rows']) && is_array($issue['affected_page_rows']) ? $issue['affected_page_rows'] : (array) ($issue['examples'] ?? array());
            foreach ($affected as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $row['site_audit_issue_type'] = sanitize_key((string) ($issue['issue_type'] ?? $issue['key'] ?? ''));
                $row['site_audit_issue_id'] = sanitize_text_field((string) ($issue['issue_id'] ?? ''));
                $row['site_audit_category'] = sanitize_key((string) ($issue['category'] ?? ''));
                $rows[] = $row;
            }
        }
        return $rows;
    }

    private function aysa_technical_site_audit_row_url($row) {
        foreach (array('url', 'address', 'source_url', 'source', 'image_url', 'src') as $key) {
            $url = esc_url_raw((string) ($row[$key] ?? ''));
            if ($url !== '') {
                return $url;
            }
        }
        return '';
    }

	    private function aysa_technical_site_audit_get_redirect_execution_package($project_id, $snapshot, $bundle, $package_id) {
	        $bundle = is_array($bundle) ? $bundle : array();
	        $prepared = isset($bundle['last_prepare_result']) && is_array($bundle['last_prepare_result']) ? $bundle['last_prepare_result'] : array();
	        $package = isset($prepared['redirect_execution_package']) && is_array($prepared['redirect_execution_package']) ? $prepared['redirect_execution_package'] : array();
	        if (!empty($package) && !empty($package['candidates'])) {
	            $package = $this->aysa_technical_site_audit_clean_redirect_execution_package($package);
	            if (!empty($package['candidates'])) {
	                return $package;
	            }
	        }
	        return $this->aysa_technical_site_audit_build_redirect_execution_package($project_id, $snapshot, $bundle, $package_id);
	    }

    private function aysa_technical_site_audit_get_internal_linking_execution_package($bundle) {
        $bundle = is_array($bundle) ? $bundle : array();
        $prepared = isset($bundle['last_prepare_result']) && is_array($bundle['last_prepare_result']) ? $bundle['last_prepare_result'] : array();
        $package = isset($prepared['internal_linking_execution_package']) && is_array($prepared['internal_linking_execution_package']) ? $prepared['internal_linking_execution_package'] : array();
        return !empty($package) && !empty($package['candidates']) ? $package : array();
    }

    private function aysa_technical_site_audit_get_image_alt_execution_package($bundle) {
        $bundle = is_array($bundle) ? $bundle : array();
        if (isset($bundle['image_alt_execution_package']) && is_array($bundle['image_alt_execution_package']) && !empty($bundle['image_alt_execution_package'])) {
            return $bundle['image_alt_execution_package'];
        }
        $prepared = isset($bundle['last_prepare_result']) && is_array($bundle['last_prepare_result']) ? $bundle['last_prepare_result'] : array();
        $package = isset($prepared['image_alt_execution_package']) && is_array($prepared['image_alt_execution_package']) ? $prepared['image_alt_execution_package'] : array();
        return !empty($package) ? $package : array();
    }

    private function aysa_technical_site_audit_get_canonical_execution_package($bundle) {
        $bundle = is_array($bundle) ? $bundle : array();
        if (isset($bundle['canonical_execution_package']) && is_array($bundle['canonical_execution_package']) && !empty($bundle['canonical_execution_package'])) {
            return $bundle['canonical_execution_package'];
        }
        $prepared = isset($bundle['last_prepare_result']) && is_array($bundle['last_prepare_result']) ? $bundle['last_prepare_result'] : array();
        $package = isset($prepared['canonical_execution_package']) && is_array($prepared['canonical_execution_package']) ? $prepared['canonical_execution_package'] : array();
        return !empty($package) ? $package : array();
    }

    private function aysa_technical_site_audit_build_internal_linking_execution_package($project_id, $snapshot, $bundle, $package_id) {
        $project_id = intval($project_id);
        $snapshot = is_array($snapshot) ? $snapshot : array();
        $bundle = is_array($bundle) ? $bundle : array();
        $package_id = sanitize_text_field((string) $package_id);
        $rows = $this->aysa_technical_site_audit_collect_bundle_rows($snapshot, $bundle);
        if (empty($rows)) {
            $rows = $this->aysa_technical_site_audit_discover_internal_linking_rows_from_wp($project_id);
        }
        $candidates = array();
        $seen = array();
        $manual = 0;
        $safe = 0;
        foreach ((array) $rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $url = $this->aysa_technical_site_audit_row_url($row);
            if ($url === '' || isset($seen[$url])) {
                continue;
            }
            $seen[$url] = true;
            $anchor = sanitize_text_field((string) ($row['suggested_anchor_text'] ?? $row['anchor_text'] ?? $row['keyword'] ?? ''));
            $sources = isset($row['suggested_source_pages']) && is_array($row['suggested_source_pages']) ? array_values($row['suggested_source_pages']) : array();
            $is_safe = !empty($sources) && $anchor !== '';
            $is_safe ? $safe++ : $manual++;
            $candidates[] = array(
                'url' => esc_url_raw($url),
                'page_type' => sanitize_key((string) ($row['page_type'] ?? $row['content_type'] ?? '')),
                'current_inlinks_count' => intval($row['inlinks'] ?? $row['inlinks_count'] ?? 0),
                'missing_inlinks' => true,
                'orphan_status' => 'missing_or_low_inlinks',
                'suggested_source_pages' => $sources,
                'suggested_anchor_text' => $anchor,
                'target_keywords' => $anchor !== '' ? array($anchor) : array(),
                'confidence' => $is_safe ? 88 : 45,
                'classification' => $is_safe ? 'safe_auto_apply' : 'manual_review_needed',
                'safe_to_apply' => $is_safe,
                'manual_review_required' => !$is_safe,
                'reason' => $is_safe ? 'source_page_contains_anchor_for_target' : 'source_pages_required_for_scoped_internal_linking',
                'inclusion_reason' => sanitize_text_field((string) ($row['inclusion_reason'] ?? 'weak_internal_linking_from_site_audit')),
            );
        }
        return array(
            'package_id' => $package_id,
            'project_id' => $project_id,
            'action_key' => 'prepare_internal_linking_plan',
            'issue_family' => 'internal_linking',
            'created_at' => current_time('mysql'),
            'source' => empty($rows) ? 'site_audit_empty' : 'site_audit_internal_linking_rows',
            'candidates' => array_values($candidates),
            'row_results' => array(),
            'total_candidates' => count($candidates),
            'safe_auto_apply_count' => $safe,
            'manual_review_needed_count' => $manual,
            'no_valid_source_count' => 0,
            'no_valid_target_count' => 0,
        );
    }

    private function aysa_technical_site_audit_discover_internal_linking_rows_from_wp($project_id) {
        $rows = array();
        $targets = get_posts(array(
            'post_type' => array('page', 'post', 'product'),
            'post_status' => 'publish',
            'numberposts' => 12,
            'orderby' => 'modified',
            'order' => 'DESC',
        ));
        $sources = get_posts(array(
            'post_type' => array('page', 'post'),
            'post_status' => 'publish',
            'numberposts' => 20,
            'orderby' => 'modified',
            'order' => 'DESC',
        ));
        foreach ((array) $targets as $target) {
            if (!$target instanceof WP_Post) {
                continue;
            }
            $target_url = get_permalink($target);
            $anchor = sanitize_text_field((string) $target->post_title);
            if (!$target_url || $anchor === '') {
                continue;
            }
            $suggested = array();
            foreach ((array) $sources as $source) {
                if (!$source instanceof WP_Post || intval($source->ID) === intval($target->ID)) {
                    continue;
                }
                $content = wp_strip_all_tags((string) $source->post_content);
                if ($content !== '' && stripos($content, $anchor) !== false) {
                    $suggested[] = array('url' => esc_url_raw((string) get_permalink($source)), 'anchor_text' => $anchor);
                    break;
                }
            }
            $rows[] = array(
                'url' => esc_url_raw((string) $target_url),
                'page_type' => sanitize_key((string) $target->post_type),
                'inlinks_count' => 0,
                'suggested_anchor_text' => $anchor,
                'suggested_source_pages' => $suggested,
                'inclusion_reason' => empty($suggested) ? 'wp_fallback_target_without_safe_source' : 'wp_fallback_source_contains_target_anchor',
            );
        }
        return $rows;
    }

    private function aysa_technical_site_audit_discover_image_alt_items_from_wp($project_id) {
        $project_id = intval($project_id);
        $items = array();
        $add_item = function ($attachment_id, $source, $page_id = 0, $page_url = '', $page_title = '') use (&$items, $project_id) {
            $attachment_id = intval($attachment_id);
            if ($attachment_id <= 0) {
                return;
            }
            $image_url = wp_get_attachment_url($attachment_id);
            if (!$image_url) {
                return;
            }
            $image_url = esc_url_raw((string) $image_url);
            if ($image_url === '') {
                return;
            }
            $mime = (string) get_post_mime_type($attachment_id);
            if (!in_array($mime, array('image/jpeg', 'image/png', 'image/webp', 'image/gif'), true)) {
                return;
            }
            $attachment_post = get_post($attachment_id);
            $parent_id = $attachment_post instanceof WP_Post ? intval($attachment_post->post_parent) : 0;
            if ($page_id <= 0 && $parent_id > 0 && get_post_status($parent_id) === 'publish') {
                $page_id = $parent_id;
            }
            $page_post = $page_id > 0 ? get_post($page_id) : null;
            if ($page_url === '' && $page_post instanceof WP_Post && get_post_status($page_id) === 'publish') {
                $page_url = (string) get_permalink($page_id);
            }
            if ($page_url === '') {
                $page_url = home_url('/');
            }
            if ($page_title === '' && $page_post instanceof WP_Post) {
                $page_title = (string) $page_post->post_title;
            }
            if ($page_title === '' && $attachment_post instanceof WP_Post) {
                $page_title = (string) $attachment_post->post_title;
            }
            $primary_keyword = $page_id > 0 ? $this->aysa_technical_site_audit_primary_keyword($page_id) : '';
            if ($primary_keyword === '') {
                $primary_keyword = sanitize_text_field($page_title);
            }
            $secondary_keywords = $page_id > 0 ? $this->aysa_technical_site_audit_secondary_keywords($page_id, $primary_keyword) : array();
            $language_context = $this->aysa_technical_site_audit_resolve_image_alt_language($project_id, $page_id);
            $existing_alt = sanitize_text_field((string) get_post_meta($attachment_id, '_wp_attachment_image_alt', true));
            $image_filename = sanitize_text_field(basename((string) wp_parse_url($image_url, PHP_URL_PATH)));
            $quality = $this->aysa_technical_site_audit_image_alt_quality($existing_alt, $image_filename, $image_url);
            $aysa_alt_generated_at = sanitize_text_field((string) get_post_meta($attachment_id, '_aysa_image_alt_generated_at', true));
            $existing_sources = isset($items[$image_url]['inventory_sources']) && is_array($items[$image_url]['inventory_sources'])
                ? $items[$image_url]['inventory_sources']
                : array();
            $existing_sources[] = sanitize_key((string) $source);
            $items[$image_url] = array(
                'attachment_id' => $attachment_id,
                'image_url' => $image_url,
                'existing_alt' => $existing_alt,
                'current_alt_quality' => $quality,
                'image_alt_bucket' => sanitize_key((string) ($quality['bucket'] ?? 'needs_review')),
                'alt_quality_reason' => sanitize_key((string) ($quality['reason'] ?? 'needs_review')),
                'aysa_alt_generated' => $aysa_alt_generated_at !== '',
                'aysa_alt_generated_at' => $aysa_alt_generated_at,
                'page_url' => esc_url_raw((string) $page_url),
                'page_title' => sanitize_text_field($page_title),
                'primary_keyword' => $primary_keyword,
                'secondary_keywords' => $secondary_keywords,
                'image_filename' => $image_filename,
                'source' => sanitize_key((string) $source),
                'inventory_source' => sanitize_key((string) $source),
                'inventory_sources' => array_values(array_unique(array_filter($existing_sources))),
                'language' => sanitize_text_field((string) ($language_context['language'] ?? 'ro')),
                'language_source' => sanitize_key((string) ($language_context['source'] ?? 'site_locale_fallback')),
                'media_optimization_eligible' => in_array($mime, array('image/jpeg', 'image/png'), true),
                'write_supported' => true,
                'mime_type' => sanitize_text_field($mime),
            );
        };

        $attachments = get_posts(array(
            'post_type' => 'attachment',
            'post_status' => 'inherit',
            'post_mime_type' => array('image/jpeg', 'image/png', 'image/webp', 'image/gif'),
            'numberposts' => 1000,
            'orderby' => 'ID',
            'order' => 'ASC',
        ));
        foreach ((array) $attachments as $attachment) {
            if ($attachment instanceof WP_Post) {
                $add_item(intval($attachment->ID), 'wp_media_library');
            }
        }

        $posts = array();
        foreach (array('product' => 320, 'page' => 120, 'post' => 120) as $post_type => $limit) {
            $posts = array_merge($posts, get_posts(array(
                'post_type' => $post_type,
                'post_status' => 'publish',
                'numberposts' => $limit,
                'orderby' => 'modified',
                'order' => 'DESC',
            )));
        }
        foreach ((array) $posts as $post) {
            if (!$post instanceof WP_Post) {
                continue;
            }
            $post_id = intval($post->ID);
            $page_url = get_permalink($post);
            if (!$page_url) {
                continue;
            }
            $attachment_sources = array();
            $thumbnail_id = intval(get_post_thumbnail_id($post_id));
            if ($thumbnail_id > 0) {
                $attachment_sources[$thumbnail_id] = $post->post_type === 'product' ? 'woocommerce_product_featured_image' : 'featured_image';
            }
            $gallery_ids = get_post_meta($post_id, '_product_image_gallery', true);
            foreach (array_filter(array_map('intval', explode(',', (string) $gallery_ids))) as $gallery_id) {
                $attachment_sources[$gallery_id] = 'woocommerce_product_gallery';
            }
            if (preg_match_all('/wp-image-(\d+)/', (string) $post->post_content, $matches) && !empty($matches[1])) {
                foreach ((array) $matches[1] as $match_id) {
                    $attachment_sources[intval($match_id)] = 'post_content_wp_image';
                }
            }
            foreach ($this->aysa_agent8v3_image_optimization_attachment_ids_from_local_post($post_id) as $elementor_attachment_id) {
                $attachment_sources[intval($elementor_attachment_id)] = 'elementor_or_gallery_image';
            }
            foreach ($attachment_sources as $attachment_id => $source) {
                $add_item((int) $attachment_id, (string) $source, $post_id, (string) $page_url, (string) $post->post_title);
            }
            if (preg_match_all('/<img\b[^>]*>/i', (string) $post->post_content, $img_matches) && !empty($img_matches[0])) {
                foreach ((array) $img_matches[0] as $img_tag) {
                    $src = '';
                    if (preg_match('/\bsrc=[\'"]([^\'"]+)[\'"]/i', (string) $img_tag, $src_match)) {
                        $src = esc_url_raw((string) $src_match[1]);
                    }
                    if ($src === '' || preg_match('/wp-image-(\d+)/', (string) $img_tag)) {
                        continue;
                    }
                    $alt = '';
                    if (preg_match('/\balt=[\'"]([^\'"]*)[\'"]/i', (string) $img_tag, $alt_match)) {
                        $alt = sanitize_text_field((string) $alt_match[1]);
                    }
                    if (isset($items[$src])) {
                        continue;
                    }
                    $quality = $this->aysa_technical_site_audit_image_alt_quality($alt, sanitize_text_field(basename((string) wp_parse_url($src, PHP_URL_PATH))), $src);
                    $items[$src] = array(
                        'attachment_id' => 0,
                        'existing_alt' => $alt,
                        'current_alt_quality' => $quality,
                        'image_alt_bucket' => 'external_unsupported',
                        'alt_quality_reason' => 'external_or_unmapped_content_image',
                        'page_url' => esc_url_raw((string) $page_url),
                        'page_title' => sanitize_text_field((string) $post->post_title),
                        'image_filename' => sanitize_text_field(basename((string) wp_parse_url($src, PHP_URL_PATH))),
                        'image_url' => $src,
                        'source' => 'post_content_unmapped_image',
                        'inventory_source' => 'post_content_unmapped_image',
                        'inventory_sources' => array('post_content_unmapped_image'),
                        'language' => sanitize_text_field((string) ($this->aysa_technical_site_audit_resolve_image_alt_language($project_id, $post_id)['language'] ?? 'ro')),
                        'language_source' => 'post_content_context',
                        'media_optimization_eligible' => false,
                        'write_supported' => false,
                        'mime_type' => '',
                    );
                }
            }
        }

        if (taxonomy_exists('product_cat')) {
            $terms = get_terms(array('taxonomy' => 'product_cat', 'hide_empty' => false, 'number' => 200));
            if (!is_wp_error($terms)) {
                foreach ((array) $terms as $term) {
                    $thumbnail_id = intval(get_term_meta(intval($term->term_id), 'thumbnail_id', true));
                    if ($thumbnail_id > 0) {
                        $term_link = get_term_link($term);
                        $add_item($thumbnail_id, 'woocommerce_category_image', 0, !is_wp_error($term_link) ? (string) $term_link : '', (string) $term->name);
                    }
                }
            }
        }

        foreach ($items as $image_url => $item) {
            $sources = array_values(array_unique(array_filter(array_map('sanitize_key', (array) ($item['inventory_sources'] ?? array())))));
            if ($sources === array('wp_media_library')) {
                // Media-library-only files have no verified public SEO surface.
                // Keep them visible as inventory evidence, but never generate or
                // bill ALT proposals until a public post/term association exists.
                $item['image_alt_bucket'] = 'needs_review';
                $item['alt_quality_reason'] = 'not_linked_to_public_content';
                $item['current_alt_quality'] = array(
                    'bucket' => 'needs_review',
                    'reason' => 'not_linked_to_public_content',
                    'confidence' => 100,
                );
                $item['write_supported'] = false;
                $item['page_url'] = '';
            }
            $items[$image_url] = $item;
        }

        $alt_counts = array();
        foreach ($items as $item) {
            $normalized = $this->aysa_technical_site_audit_normalize_alt_text((string) ($item['existing_alt'] ?? ''));
            if ($normalized !== '') {
                $alt_counts[$normalized] = intval($alt_counts[$normalized] ?? 0) + 1;
            }
        }
        foreach ($items as $image_url => $item) {
            $existing_alt = (string) ($item['existing_alt'] ?? '');
            $normalized = $this->aysa_technical_site_audit_normalize_alt_text($existing_alt);
            $bucket = sanitize_key((string) ($item['image_alt_bucket'] ?? 'needs_review'));
            if ($normalized !== '' && intval($alt_counts[$normalized] ?? 0) > 1 && !in_array($bucket, array('decorative_icon', 'external_unsupported', 'needs_review'), true)) {
                $item['image_alt_bucket'] = 'duplicate_alt';
                $item['alt_quality_reason'] = 'same_alt_used_on_multiple_images';
                $item['current_alt_quality'] = array(
                    'bucket' => 'duplicate_alt',
                    'reason' => 'same_alt_used_on_multiple_images',
                    'confidence' => 90,
                );
            }
            $items[$image_url] = $item;
        }

        uasort($items, function ($a, $b) {
            $priority = array(
                'missing_alt' => 10,
                'weak_alt' => 20,
                'duplicate_alt' => 30,
                'needs_review' => 40,
                'external_unsupported' => 50,
                'decorative_icon' => 60,
                'already_good' => 90,
            );
            $a_bucket = sanitize_key((string) ($a['image_alt_bucket'] ?? 'needs_review'));
            $b_bucket = sanitize_key((string) ($b['image_alt_bucket'] ?? 'needs_review'));
            $a_priority = intval($priority[$a_bucket] ?? 50);
            $b_priority = intval($priority[$b_bucket] ?? 50);
            if ($a_priority === $b_priority) {
                return intval($a['attachment_id'] ?? 0) <=> intval($b['attachment_id'] ?? 0);
            }
            return $a_priority <=> $b_priority;
        });

        return $items;
    }

    private function aysa_technical_site_audit_normalize_alt_text($alt) {
        $normalized = strtolower(trim((string) $alt));
        $normalized = preg_replace('/\s+/u', ' ', $normalized);
        return is_string($normalized) ? $normalized : '';
    }

    private function aysa_technical_site_audit_image_alt_quality($alt, $filename = '', $image_url = '') {
        $alt = trim((string) $alt);
        $filename = trim((string) $filename);
        $haystack = strtolower($alt . ' ' . $filename . ' ' . basename((string) wp_parse_url((string) $image_url, PHP_URL_PATH)));
        if ($alt === '') {
            return array('bucket' => 'missing_alt', 'reason' => 'alt_text_empty', 'confidence' => 100);
        }
        if (preg_match('/\b(logo|icon|favicon|sprite|avatar|placeholder|decorativ|decorative)\b/i', $haystack)) {
            return array('bucket' => 'decorative_icon', 'reason' => 'decorative_logo_or_icon', 'confidence' => 88);
        }
        $normalized = $this->aysa_technical_site_audit_normalize_alt_text($alt);
        $filename_stem = strtolower((string) preg_replace('/\.[a-z0-9]+$/i', '', $filename));
        $filename_stem = (string) preg_replace('/[-_]+/', ' ', $filename_stem);
        $weak_terms = array('image', 'img', 'poza', 'foto', 'photograph', 'picture', 'screenshot', 'untitled', 'dsc', 'whatsapp image');
        if (in_array($normalized, $weak_terms, true) || mb_strlen($normalized) < 4) {
            return array('bucket' => 'weak_alt', 'reason' => 'generic_alt_text', 'confidence' => 92);
        }
        if ($filename_stem !== '' && $normalized === $this->aysa_technical_site_audit_normalize_alt_text($filename_stem)) {
            return array('bucket' => 'weak_alt', 'reason' => 'alt_matches_filename', 'confidence' => 90);
        }
        if (preg_match('/^(img|dsc|image|photo|screenshot)[-_ ]?\d+/i', $normalized)) {
            return array('bucket' => 'weak_alt', 'reason' => 'camera_filename_alt', 'confidence' => 94);
        }
        return array('bucket' => 'already_good', 'reason' => 'existing_alt_descriptive', 'confidence' => 78);
    }

    private function aysa_technical_site_audit_image_alt_inventory_summary($items) {
        $summary = array(
            'total_images' => 0,
            'total_media_library_images' => 0,
            'missing_alt' => 0,
            'weak_alt' => 0,
            'duplicate_alt' => 0,
            'already_good' => 0,
            'decorative_icon' => 0,
            'external_unsupported' => 0,
            'needs_review' => 0,
            'safe_update_candidates' => 0,
            'write_supported' => 0,
            'by_source' => array(),
        );
        foreach ((array) $items as $item) {
            if (!is_array($item)) {
                continue;
            }
            $summary['total_images']++;
            $bucket = sanitize_key((string) ($item['image_alt_bucket'] ?? 'needs_review'));
            if (!array_key_exists($bucket, $summary)) {
                $summary['needs_review']++;
            } else {
                $summary[$bucket]++;
            }
            if (!empty($item['write_supported'])) {
                $summary['write_supported']++;
            }
            if (intval($item['attachment_id'] ?? 0) > 0) {
                $summary['total_media_library_images']++;
            }
            if (in_array($bucket, array('missing_alt', 'weak_alt', 'duplicate_alt'), true) && !empty($item['write_supported'])) {
                $summary['safe_update_candidates']++;
            }
            foreach ((array) ($item['inventory_sources'] ?? array($item['inventory_source'] ?? $item['source'] ?? 'unknown')) as $source) {
                $source = sanitize_key((string) $source);
                if ($source === '') {
                    continue;
                }
                if (!isset($summary['by_source'][$source])) {
                    $summary['by_source'][$source] = 0;
                }
                $summary['by_source'][$source]++;
            }
        }
        return $summary;
    }

    private function aysa_image_optimization_inventory_key($project_id) {
        return 'aysa_image_optimization_inventory_' . intval($project_id);
    }

    private function aysa_image_optimization_cursor_key($project_id) {
        return 'aysa_image_optimization_cursor_' . intval($project_id);
    }

    private function aysa_image_optimization_get_inventory($project_id) {
        $inventory = get_option($this->aysa_image_optimization_inventory_key($project_id), array());
        return is_array($inventory) ? $inventory : array();
    }

    private function aysa_image_optimization_save_inventory($project_id, $inventory) {
        update_option($this->aysa_image_optimization_inventory_key($project_id), is_array($inventory) ? $inventory : array(), false);
    }

    private function aysa_image_optimization_scan_inventory($project_id, $payload = array()) {
        $project_id = intval($project_id);
        $payload = is_array($payload) ? $payload : array();
        $scan_batch_size = max(20, min(500, intval($payload['scan_batch_size'] ?? 200)));
        $optimize_batch_size = max(1, min(25, intval($payload['optimize_batch_size'] ?? 10)));
        $force = !empty($payload['force_inventory_reset']) || !empty($payload['force']);
        $cursor_key = $this->aysa_image_optimization_cursor_key($project_id);
        if ($force) {
            delete_option($this->aysa_image_optimization_inventory_key($project_id));
            delete_option($cursor_key);
        }
        $cursor = max(0, intval(get_option($cursor_key, 0)));
        $inventory = $this->aysa_image_optimization_get_inventory($project_id);
        $attachments = get_posts(array(
            'post_type' => 'attachment',
            'post_status' => 'inherit',
            'post_mime_type' => array('image/jpeg', 'image/png'),
            'numberposts' => $scan_batch_size,
            'offset' => $cursor,
            'orderby' => 'ID',
            'order' => 'ASC',
            'fields' => 'ids',
        ));
        foreach ((array) $attachments as $attachment_id) {
            $attachment_id = intval($attachment_id);
            if ($attachment_id <= 0) {
                continue;
            }
            $row = $this->aysa_image_optimization_inventory_row($project_id, $attachment_id);
            if ($row === array()) {
                continue;
            }
            $existing = isset($inventory[$attachment_id]) && is_array($inventory[$attachment_id]) ? $inventory[$attachment_id] : array();
            $status = sanitize_key((string) ($existing['status'] ?? 'pending'));
            $attempts = intval($existing['attempts'] ?? 0);
            $last_error = sanitize_text_field((string) ($existing['last_error'] ?? ''));
            if ($status !== 'pending') {
                $row['status'] = $status;
            }
            $inventory[$attachment_id] = array_merge($row, array(
                'status' => sanitize_key((string) ($row['status'] ?? $status ?: 'pending')),
                'attempts' => $attempts,
                'last_error' => $last_error,
                'last_seen_at' => current_time('mysql'),
            ));
        }
        $scanned = count((array) $attachments);
        $has_more_scan = $scanned >= $scan_batch_size;
        update_option($cursor_key, $has_more_scan ? $cursor + $scanned : -1, false);
        $this->aysa_image_optimization_save_inventory($project_id, $inventory);
        return array(
            'applied' => true,
            'operation' => 'site_audit_image_inventory_scan',
            'scan_batch_size' => $scan_batch_size,
            'scanned_count' => $scanned,
            'next_cursor' => $has_more_scan ? $cursor + $scanned : -1,
            'has_more_scan' => $has_more_scan,
            'inventory_summary' => $this->aysa_image_optimization_inventory_summary($inventory),
            'image_optimization_batch' => $this->aysa_image_optimization_next_inventory_batch($project_id, $inventory, $optimize_batch_size),
        );
    }

    private function aysa_image_optimization_inventory_row($project_id, $attachment_id) {
        $attachment_id = intval($attachment_id);
        $file = get_attached_file($attachment_id);
        if (!is_string($file) || $file === '' || !file_exists($file) || !is_readable($file)) {
            return array(
                'attachment_id' => $attachment_id,
                'status' => 'failed_final',
                'last_error' => 'attachment_file_missing',
                'attempts' => 0,
                'updated_at' => current_time('mysql'),
            );
        }
        $mime = (string) get_post_mime_type($attachment_id);
        if (!in_array($mime, array('image/jpeg', 'image/png'), true)) {
            return array();
        }
        $image_url = wp_get_attachment_url($attachment_id);
        if (!$image_url) {
            return array();
        }
        $context = $this->aysa_image_optimization_attachment_context($project_id, $attachment_id);
        $optimized = get_post_meta($attachment_id, '_aysa_image_optimization', true);
        $already_optimized = is_array($optimized) && !empty($optimized['webp_url']) && $this->aysa_image_optimization_webp_file_exists($optimized);
        $existing_alt = sanitize_text_field((string) get_post_meta($attachment_id, '_wp_attachment_image_alt', true));
        $aysa_alt_generated_at = sanitize_text_field((string) get_post_meta($attachment_id, '_aysa_image_alt_generated_at', true));
        return array(
            'attachment_id' => $attachment_id,
            'status' => $already_optimized ? 'optimized' : 'pending',
            'attempts' => 0,
            'mime_type' => sanitize_text_field($mime),
            'file_path' => sanitize_text_field($file),
            'image_url' => esc_url_raw((string) $image_url),
            'image_filename' => sanitize_text_field(basename((string) wp_parse_url((string) $image_url, PHP_URL_PATH))),
            'existing_alt' => $existing_alt,
            'aysa_alt_generated' => $aysa_alt_generated_at !== '',
            'aysa_alt_generated_at' => $aysa_alt_generated_at,
            'media_optimization_eligible' => !$already_optimized,
            'alt_needs_generation' => $existing_alt === '',
            'page_url' => esc_url_raw((string) ($context['page_url'] ?? home_url('/'))),
            'page_title' => sanitize_text_field((string) ($context['page_title'] ?? get_the_title($attachment_id))),
            'primary_keyword' => sanitize_text_field((string) ($context['primary_keyword'] ?? '')),
            'secondary_keywords' => isset($context['secondary_keywords']) && is_array($context['secondary_keywords']) ? array_values($context['secondary_keywords']) : array(),
            'context_source' => sanitize_key((string) ($context['context_source'] ?? 'attachment_fallback')),
            'aysa_image_context' => $context,
            'updated_at' => current_time('mysql'),
        );
    }

    private function aysa_image_optimization_attachment_context($project_id, $attachment_id) {
        global $wpdb;
        $attachment_id = intval($attachment_id);
        $thumbnail_posts = get_posts(array(
            'post_type' => array('product', 'page', 'post'),
            'post_status' => 'publish',
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reviewed WordPress query: the metadata selector is required by the feature and is constrained by the surrounding post type, status, limit, or administrative scope.
            'meta_key' => '_thumbnail_id',
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Reviewed WordPress query: the metadata selector is required by the feature and is constrained by the surrounding post type, status, limit, or administrative scope.
            'meta_value' => $attachment_id,
            'numberposts' => 1,
            'orderby' => 'modified',
            'order' => 'DESC',
            'fields' => 'ids',
        ));
        $post_id = intval($thumbnail_posts[0] ?? 0);
        $context_source = $post_id > 0 ? 'featured_image' : '';
        if ($post_id <= 0) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $post_id = intval($wpdb->get_var($wpdb->prepare(
                "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_product_image_gallery' AND meta_value LIKE %s ORDER BY post_id DESC LIMIT 1",
                '%' . $wpdb->esc_like((string) $attachment_id) . '%'
            )));
            $context_source = $post_id > 0 ? 'product_gallery' : '';
        }
        if ($post_id <= 0) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $post_id = intval($wpdb->get_var($wpdb->prepare(
                "SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type IN ('product','page','post') AND post_content LIKE %s ORDER BY FIELD(post_type, 'product', 'page', 'post') ASC, ID DESC LIMIT 1",
                '%' . $wpdb->esc_like('wp-image-' . $attachment_id) . '%'
            )));
            $context_source = $post_id > 0 ? 'content_image' : '';
        }
        $attachment = get_post($attachment_id);
        if ($post_id <= 0 && $attachment instanceof WP_Post && intval($attachment->post_parent) > 0) {
            $post_id = intval($attachment->post_parent);
            $context_source = 'attachment_parent';
        }
        $post = $post_id > 0 ? get_post($post_id) : null;
        $page_title = $post instanceof WP_Post ? (string) $post->post_title : ($attachment instanceof WP_Post ? (string) $attachment->post_title : '');
        $primary_keyword = $post instanceof WP_Post ? $this->aysa_technical_site_audit_primary_keyword($post_id) : '';
        if ($primary_keyword === '') {
            $primary_keyword = sanitize_text_field($page_title);
        }
        return array(
            'post_id' => $post instanceof WP_Post ? intval($post->ID) : 0,
            'page_url' => $post instanceof WP_Post && get_post_status($post->ID) === 'publish' ? esc_url_raw((string) get_permalink($post)) : home_url('/'),
            'page_title' => sanitize_text_field($page_title),
            'primary_keyword' => sanitize_text_field($primary_keyword),
            'secondary_keywords' => $post instanceof WP_Post ? $this->aysa_technical_site_audit_secondary_keywords($post_id, $primary_keyword) : array(),
            'context_source' => $context_source !== '' ? $context_source : 'attachment_fallback',
        );
    }

    private function aysa_image_optimization_next_inventory_batch($project_id, $inventory, $limit) {
        $limit = max(1, intval($limit));
        $rows = array();
        foreach ((array) $inventory as $attachment_id => $row) {
            if (!is_array($row) || count($rows) >= $limit) {
                continue;
            }
            $status = sanitize_key((string) ($row['status'] ?? 'pending'));
            $attempts = intval($row['attempts'] ?? 0);
            if ($status !== 'pending' && !($status === 'failed_retryable' && $attempts < 3)) {
                continue;
            }
            $rows[] = $row;
        }
        return array(
            'items' => $rows,
            'count' => count($rows),
            'has_pending' => count($rows) > 0,
        );
    }

    private function aysa_image_optimization_inventory_summary($inventory) {
        $summary = array(
            'total' => 0,
            'pending' => 0,
            'processing' => 0,
            'optimized' => 0,
            'skipped' => 0,
            'failed_retryable' => 0,
            'failed_final' => 0,
        );
        foreach ((array) $inventory as $row) {
            if (!is_array($row)) {
                continue;
            }
            $summary['total']++;
            $status = sanitize_key((string) ($row['status'] ?? 'pending'));
            if (!isset($summary[$status])) {
                $status = 'pending';
            }
            $summary[$status]++;
        }
        return $summary;
    }

    private function aysa_image_optimization_apply_inventory_batch($project_id, $payload = array()) {
        $project_id = intval($project_id);
        $payload = is_array($payload) ? $payload : array();
        $package = isset($payload['image_alt_execution_package']) && is_array($payload['image_alt_execution_package'])
            ? $payload['image_alt_execution_package']
            : (isset($payload['execution_package']) && is_array($payload['execution_package']) ? $payload['execution_package'] : array());
        if ($package === array()) {
            return array('applied' => false, 'reason' => 'image_optimization_package_missing');
        }
        $result = $this->aysa_technical_site_audit_apply_image_alt_package($project_id, $package);
        $inventory = $this->aysa_image_optimization_get_inventory($project_id);
        foreach ((array) ($result['results'] ?? array()) as $row) {
            if (!is_array($row)) {
                continue;
            }
            $attachment_id = intval($row['attachment_id'] ?? 0);
            if ($attachment_id <= 0 || !isset($inventory[$attachment_id]) || !is_array($inventory[$attachment_id])) {
                continue;
            }
            $media = isset($row['image_optimization']) && is_array($row['image_optimization']) ? $row['image_optimization'] : array();
            $status = sanitize_key((string) ($row['status'] ?? ''));
            $reason = sanitize_key((string) ($row['reason'] ?? ($media['reason'] ?? '')));
            $optimized = in_array($status, array('success', 'already_present'), true) || in_array((string) ($media['status'] ?? ''), array('optimized', 'already_optimized'), true);
            if ($optimized) {
                $inventory[$attachment_id]['status'] = 'optimized';
                $inventory[$attachment_id]['last_error'] = '';
            } elseif (in_array($reason, array('unsupported_mime_type', 'keyword_context_missing', 'attachment_mapping_missing'), true)) {
                $inventory[$attachment_id]['status'] = 'skipped';
                $inventory[$attachment_id]['last_error'] = $reason;
            } else {
                $attempts = intval($inventory[$attachment_id]['attempts'] ?? 0) + 1;
                $inventory[$attachment_id]['attempts'] = $attempts;
                $inventory[$attachment_id]['status'] = $attempts >= 3 ? 'failed_final' : 'failed_retryable';
                $inventory[$attachment_id]['last_error'] = $reason !== '' ? $reason : 'image_optimization_apply_failed';
            }
            $inventory[$attachment_id]['updated_at'] = current_time('mysql');
        }
        $this->aysa_image_optimization_save_inventory($project_id, $inventory);
        return array(
            'applied' => true,
            'operation' => 'site_audit_image_optimization_batch_apply',
            'apply_result' => $result,
            'inventory_summary' => $this->aysa_image_optimization_inventory_summary($inventory),
        );
    }

    private function aysa_image_optimization_apply_single($project_id, $payload = array()) {
        $project_id = intval($project_id);
        $payload = is_array($payload) ? $payload : array();
        $row = isset($payload['row']) && is_array($payload['row']) ? $payload['row'] : array();
        if ($row === array()) {
            $package = isset($payload['image_alt_execution_package']) && is_array($payload['image_alt_execution_package'])
                ? $payload['image_alt_execution_package']
                : (isset($payload['execution_package']) && is_array($payload['execution_package']) ? $payload['execution_package'] : array());
            $items = isset($package['items']) && is_array($package['items']) ? $package['items'] : array();
            $row = isset($items[0]) && is_array($items[0]) ? $items[0] : array();
        }
        $attachment_id = intval($payload['attachment_id'] ?? ($row['attachment_id'] ?? 0));
        if ($attachment_id <= 0) {
            return array('applied' => false, 'operation' => 'site_audit_image_optimization_single_apply', 'reason' => 'attachment_required');
        }

        $inventory = $this->aysa_image_optimization_get_inventory($project_id);
        $context = isset($inventory[$attachment_id]) && is_array($inventory[$attachment_id]) ? $inventory[$attachment_id] : array();
        $row = array_merge($context, $row, array(
            'attachment_id' => $attachment_id,
            'media_optimization_eligible' => true,
        ));

        try {
            $media = $this->aysa_technical_site_audit_optimize_image_attachment($project_id, $attachment_id, $row);
        } catch (Throwable $e) {
            if (function_exists('aysa_report_operational_error')) {
                aysa_report_operational_error(array(
                    'source' => 'plugin',
                    'module' => 'technical',
                    'severity' => 'error',
                    'error_code' => 'image_optimization_exception',
                    'message_safe' => 'Image optimization threw an exception.',
                    'project_id' => $project_id,
                    'action_key' => 'site_audit_image_optimization_single_apply',
                    'technical_context' => array(
                        'attachment_id' => $attachment_id,
                        'exception_class' => get_class($e),
                        'exception_message' => $e->getMessage(),
                    ),
                ));
            }
            $media = array(
                'status' => 'failed_retryable',
                'reason' => 'image_optimization_exception',
            );
        }
        $status = sanitize_key((string) ($media['status'] ?? ''));
        $reason = sanitize_key((string) ($media['reason'] ?? ''));
        $optimized = in_array($status, array('optimized', 'already_optimized'), true)
            || !empty($media['webp_converted'])
            || !empty($media['metadata_promoted']);
        $skipped_reasons = array('unsupported_mime_type', 'keyword_context_missing', 'attachment_file_missing', 'attachment_mapping_missing', 'webp_not_smaller', 'not_attachment');
        $skipped = !$optimized && in_array($reason, $skipped_reasons, true);

        if (!isset($inventory[$attachment_id]) || !is_array($inventory[$attachment_id])) {
            $inventory[$attachment_id] = array('attachment_id' => $attachment_id);
        }
        if ($optimized) {
            $inventory[$attachment_id]['status'] = 'optimized';
            $inventory[$attachment_id]['last_error'] = '';
        } elseif ($skipped) {
            $inventory[$attachment_id]['status'] = 'skipped';
            $inventory[$attachment_id]['last_error'] = $reason;
        } else {
            $attempts = intval($inventory[$attachment_id]['attempts'] ?? 0) + 1;
            $inventory[$attachment_id]['attempts'] = $attempts;
            $inventory[$attachment_id]['status'] = $attempts >= 3 ? 'failed_final' : 'failed_retryable';
            $inventory[$attachment_id]['last_error'] = $reason !== '' ? $reason : 'image_optimization_apply_failed';
        }
        $inventory[$attachment_id]['updated_at'] = current_time('mysql');
        $this->aysa_image_optimization_save_inventory($project_id, $inventory);

        return array(
            'applied' => true,
            'operation' => 'site_audit_image_optimization_single_apply',
            'attachment_id' => $attachment_id,
            'image_optimization' => $media,
            'applied_count' => $optimized ? 1 : 0,
            'skipped_count' => $skipped ? 1 : 0,
            'manual_needed_count' => 0,
            'failed_count' => (!$optimized && !$skipped) ? 1 : 0,
            'webp_converted_count' => !empty($media['webp_converted']) ? 1 : 0,
            'webp_skipped_count' => empty($media['webp_converted']) ? 1 : 0,
            'seo_renamed_count' => !empty($media['seo_renamed']) ? 1 : 0,
            'preserved_alt_optimized_count' => $optimized ? 1 : 0,
            'inventory_summary' => $this->aysa_image_optimization_inventory_summary($inventory),
        );
    }

    private function aysa_image_optimization_mark_inventory_failed($project_id, $payload = array()) {
        $project_id = intval($project_id);
        $payload = is_array($payload) ? $payload : array();
        $attachment_ids = isset($payload['attachment_ids']) && is_array($payload['attachment_ids'])
            ? array_map('intval', $payload['attachment_ids'])
            : array(intval($payload['attachment_id'] ?? 0));
        $reason = sanitize_key((string) ($payload['reason'] ?? 'image_optimization_apply_failed'));
        $inventory = $this->aysa_image_optimization_get_inventory($project_id);
        $updated = 0;
        foreach ($attachment_ids as $attachment_id) {
            if ($attachment_id <= 0 || !isset($inventory[$attachment_id]) || !is_array($inventory[$attachment_id])) {
                continue;
            }
            $attempts = intval($inventory[$attachment_id]['attempts'] ?? 0) + 1;
            $inventory[$attachment_id]['attempts'] = $attempts;
            $inventory[$attachment_id]['status'] = $attempts >= 3 ? 'failed_final' : 'failed_retryable';
            $inventory[$attachment_id]['last_error'] = $reason !== '' ? $reason : 'image_optimization_apply_failed';
            $inventory[$attachment_id]['updated_at'] = current_time('mysql');
            $updated++;
        }
        $this->aysa_image_optimization_save_inventory($project_id, $inventory);
        return array(
            'applied' => true,
            'operation' => 'site_audit_image_optimization_mark_failed',
            'updated_count' => $updated,
            'inventory_summary' => $this->aysa_image_optimization_inventory_summary($inventory),
        );
    }

    private function aysa_technical_site_audit_attachment_id_from_image_url($image_url) {
        global $wpdb;
        $image_url = esc_url_raw((string) $image_url);
        if ($image_url === '') {
            return 0;
        }
        $attachment_id = intval(attachment_url_to_postid($image_url));
        if ($attachment_id > 0) {
            return $attachment_id;
        }
        $path = (string) wp_parse_url($image_url, PHP_URL_PATH);
        $basename = sanitize_file_name(basename($path));
        if ($basename === '') {
            return 0;
        }
        $candidate = preg_replace('/-\d+x\d+(\.[a-z0-9]+)$/i', '$1', $basename);
        $candidate = is_string($candidate) && $candidate !== '' ? sanitize_file_name($candidate) : $basename;
        foreach (array_unique(array($candidate, $basename)) as $file) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $found = intval($wpdb->get_var($wpdb->prepare(
                "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_wp_attached_file' AND meta_value LIKE %s ORDER BY post_id DESC LIMIT 1",
                '%' . $wpdb->esc_like('/' . $file)
            )));
            if ($found > 0) {
                return $found;
            }
        }
        return 0;
    }

    private function aysa_technical_site_audit_discover_canonical_posts_from_wp($project_id) {
        $posts = array();
        $items = get_posts(array(
            'post_type' => array('page', 'post', 'product'),
            'post_status' => 'publish',
            'numberposts' => 24,
            'orderby' => 'modified',
            'order' => 'DESC',
        ));
        foreach ((array) $items as $post) {
            if (!$post instanceof WP_Post) {
                continue;
            }
            $permalink = get_permalink($post);
            if (!$permalink) {
                continue;
            }
            $posts[esc_url_raw((string) $permalink)] = array(
                'post_id' => intval($post->ID),
                'post_type' => sanitize_key((string) $post->post_type),
                'post_status' => sanitize_key((string) $post->post_status),
                'page_title' => sanitize_text_field((string) $post->post_title),
                'permalink' => esc_url_raw((string) $permalink),
                'current_canonical_url' => $this->aysa_technical_site_audit_current_post_canonical_url($post->ID),
                'canonical_provider' => $this->aysa_technical_site_audit_detect_canonical_provider($post->ID),
                'gsc_signals' => array(),
                'source' => 'wp_canonical_mapping_fallback',
            );
        }
        return $posts;
    }

    private function aysa_technical_site_audit_apply_redirect_package($project_id, $package) {
        $project_id = intval($project_id);
        $package = is_array($package) ? $package : array();
        $results = array();
        $applied = 0;
        $skipped = 0;
        $failed = 0;
        $manual = 0;
        foreach ((array) ($package['candidates'] ?? array()) as $candidate) {
            if (!is_array($candidate)) {
                continue;
            }
            $source = esc_url_raw((string) ($candidate['source_url'] ?? ''));
            $target = esc_url_raw((string) ($candidate['proposed_target_url'] ?? ''));
            $classification = sanitize_key((string) ($candidate['classification'] ?? 'no_valid_target'));
            if ($classification !== 'safe_auto_apply') {
                $classification === 'manual_review_needed' ? $manual++ : $skipped++;
                $results[] = array_merge($candidate, array(
                    'status' => $classification === 'manual_review_needed' ? 'manual_needed' : 'skipped',
                    'reason' => sanitize_text_field((string) ($candidate['reason'] ?? 'redirect_target_not_safe_for_auto_apply')),
                ));
                continue;
            }
            if ($source === '' || $target === '') {
                $skipped++;
                $results[] = array_merge($candidate, array('status' => 'skipped', 'reason' => 'redirect_source_or_target_missing'));
                continue;
            }
            if (!$this->aysa_technical_site_audit_redirect_source_is_safe($source)) {
                $skipped++;
                $results[] = array_merge($candidate, array('status' => 'skipped', 'reason' => 'redirect_source_protected_path'));
                continue;
            }
            if (!$this->aysa_technical_site_audit_redirect_target_is_safe($source, $target)) {
                $skipped++;
                $results[] = array_merge($candidate, array('status' => 'skipped', 'reason' => 'redirect_target_not_safe_for_auto_apply'));
                continue;
            }
            if ($this->aysa_technical_site_audit_redirect_rule_exists($source, $target)) {
                $applied++;
                $results[] = array_merge($candidate, array('status' => 'already_present', 'shared_executor' => 'apply_technical_redirect_candidate'));
                continue;
            }
            $result = $this->aysa_agent8_apply_technical_redirect_candidate($project_id, array('source_url' => $source, 'target_url' => $target));
            if (!empty($result['applied'])) {
                $applied++;
                $results[] = array_merge($candidate, array('status' => 'success', 'shared_executor' => 'apply_technical_redirect_candidate'));
            } else {
                $failed++;
                $results[] = array_merge($candidate, array('status' => 'failed', 'reason' => sanitize_text_field((string) ($result['reason'] ?? 'redirect_apply_failed'))));
            }
        }
        return array(
            'shared_executor' => 'apply_technical_redirect_candidate',
            'executor_path' => 'Site Audit worker -> aysa_agent8_apply_technical_redirect_candidate -> aysa_add_htaccess_redirect_rule',
            'package_id' => sanitize_text_field((string) ($package['package_id'] ?? '')),
            'total_candidates' => intval($package['total_candidates'] ?? count((array) ($package['candidates'] ?? array()))),
            'safe_auto_apply_count' => intval($package['safe_auto_apply_count'] ?? 0),
            'manual_review_needed_count' => intval($package['manual_review_needed_count'] ?? 0),
            'no_valid_target_count' => intval($package['no_valid_target_count'] ?? 0),
            'applied_count' => $applied,
            'skipped_count' => $skipped,
            'failed_count' => $failed,
            'manual_needed_count' => $manual,
            'results' => $results,
        );
    }

    private function aysa_technical_site_audit_redirect_rule_exists($source, $target) {
        $source = $this->aysa_normalize_redirect_source($source);
        $target = $this->aysa_normalize_redirect_destination($target);
        if ($source === '' || $target === '') {
            return false;
        }
        $htaccess_file = ABSPATH . '.htaccess';
        if (!file_exists($htaccess_file)) {
            return false;
        }
        $content = file_get_contents($htaccess_file);
        if (!is_string($content)) {
            return false;
        }
        $rule = 'RedirectMatch 301 ^' . preg_quote($source, '/') . '$ ' . $target;
        return strpos($content, $rule) !== false;
    }

    private function aysa_technical_site_audit_apply_redirect_rows($project_id, $rows) {
        $results = array();
        $applied = 0;
        $skipped = 0;
        $failed = 0;
        foreach ((array) $rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $source = $this->aysa_technical_site_audit_row_url($row);
            $target = '';
            foreach (array('target_url', 'redirect_url', 'destination', 'final_url', 'canonical_url') as $key) {
                $target = esc_url_raw((string) ($row[$key] ?? ''));
                if ($target !== '') {
                    break;
                }
            }
            if ($source === '' || $target === '' || untrailingslashit($source) === untrailingslashit($target)) {
                $skipped++;
                $results[] = array('url' => $source, 'target_url' => $target, 'status' => 'skipped', 'reason' => 'safe_redirect_target_missing');
                continue;
            }
            $result = $this->aysa_agent8_apply_technical_redirect_candidate($project_id, array('source_url' => $source, 'target_url' => $target));
            if (!empty($result['applied'])) {
                $applied++;
                $results[] = array('url' => $source, 'target_url' => $target, 'status' => 'success', 'shared_executor' => 'apply_technical_redirect_candidate');
            } else {
                $failed++;
                $results[] = array('url' => $source, 'target_url' => $target, 'status' => 'failed', 'reason' => sanitize_text_field((string) ($result['reason'] ?? 'redirect_apply_failed')));
            }
        }
        return array('shared_executor' => 'apply_technical_redirect_candidate', 'applied_count' => $applied, 'skipped_count' => $skipped, 'failed_count' => $failed, 'manual_needed_count' => 0, 'results' => $results);
    }

    private function aysa_technical_site_audit_apply_indexability_rows($project_id, $rows) {
        $results = array();
        $applied = 0;
        $skipped = 0;
        $manual = 0;
        foreach ((array) $rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $url = $this->aysa_technical_site_audit_row_url($row);
            $issue_type = sanitize_key((string) ($row['site_audit_issue_type'] ?? ''));
            if ($url === '' || strpos($issue_type, 'canonicals_missing') === false) {
                $manual++;
                $results[] = array('url' => $url, 'status' => 'manual_needed', 'reason' => 'unsafe_indexability_or_canonical_change_requires_review');
                continue;
            }
            $post_id = url_to_postid($url);
            if ($post_id <= 0) {
                $skipped++;
                $results[] = array('url' => $url, 'status' => 'skipped', 'reason' => 'no_clear_wordpress_post_mapping');
                continue;
            }
            $canonical = get_permalink($post_id);
            if (!$canonical) {
                $skipped++;
                $results[] = array('url' => $url, 'post_id' => $post_id, 'status' => 'skipped', 'reason' => 'canonical_permalink_unavailable');
                continue;
            }
            $manual++;
            $results[] = array('url' => $url, 'post_id' => $post_id, 'canonical_url' => esc_url_raw($canonical), 'status' => 'manual_needed', 'reason' => 'indexability_canonical_executor_not_mature', 'shared_executor' => 'site_audit_indexability_canonical_adapter');
        }
        return array('shared_executor' => 'site_audit_indexability_canonical_adapter', 'applied_count' => $applied, 'skipped_count' => $skipped, 'failed_count' => 0, 'manual_needed_count' => $manual, 'results' => $results);
    }

    private function aysa_technical_site_audit_apply_canonical_package($project_id, $package) {
        $package = is_array($package) ? $package : array();
        $results = array();
        $applied = 0;
        $skipped = 0;
        $failed = 0;
        $manual = 0;
        foreach ((array) ($package['items'] ?? array()) as $row) {
            if (!is_array($row)) {
                continue;
            }
            $post_id = intval($row['post_id'] ?? 0);
            $page_url = esc_url_raw((string) ($row['page_url'] ?? ''));
            $target_url = esc_url_raw((string) ($row['recommended_target_url'] ?? $row['proposed_value'] ?? ''));
            $action = sanitize_key((string) ($row['recommended_action'] ?? 'manual_review'));
            $confidence = intval($row['confidence'] ?? 0);
            $provider = sanitize_key((string) ($row['canonical_provider'] ?? ''));
            if ($post_id <= 0 || $target_url === '') {
                $skipped++;
                $results[] = array('page_url' => $page_url, 'post_id' => $post_id, 'status' => 'skipped', 'reason' => 'canonical_target_or_post_missing');
                continue;
            }
            if (!in_array($action, array('self_canonical', 'rewrite'), true) || $confidence < 70) {
                $manual++;
                $results[] = array('page_url' => $page_url, 'post_id' => $post_id, 'status' => 'manual_needed', 'reason' => 'canonical_requires_manual_review');
                continue;
            }
            $update = $this->aysa_technical_site_audit_update_post_canonical_url($post_id, $target_url, $provider);
            if (!empty($update['applied'])) {
                $applied++;
                $results[] = array(
                    'page_url' => $page_url,
                    'post_id' => $post_id,
                    'status' => 'success',
                    'shared_executor' => 'site_audit_canonical_adapter',
                    'recommended_action' => $action,
                    'expected_canonical' => $target_url,
                    // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reviewed WordPress query: the metadata selector is required by the feature and is constrained by the surrounding post type, status, limit, or administrative scope.
                    'meta_key' => sanitize_text_field((string) ($update['meta_key'] ?? '')),
                );
            } else {
                $failed++;
                $results[] = array('page_url' => $page_url, 'post_id' => $post_id, 'status' => 'failed', 'reason' => sanitize_text_field((string) ($update['reason'] ?? 'canonical_update_failed')));
            }
        }
        foreach ((array) ($package['skipped'] ?? array()) as $row) {
            if (!is_array($row)) {
                continue;
            }
            $decision = sanitize_key((string) ($row['decision'] ?? ($row['candidate_quality']['decision'] ?? 'manual_review')));
            if ($decision === 'preserve') {
                $skipped++;
                $results[] = array('page_url' => esc_url_raw((string) ($row['page_url'] ?? '')), 'status' => 'skipped', 'reason' => 'canonical_already_good');
            } else {
                $manual++;
                $results[] = array('page_url' => esc_url_raw((string) ($row['page_url'] ?? '')), 'status' => 'manual_needed', 'reason' => sanitize_key((string) ($row['reason'] ?? 'canonical_requires_manual_review')));
            }
        }
        return array(
            'shared_executor' => 'site_audit_canonical_adapter',
            'applied_count' => $applied,
            'skipped_count' => $skipped,
            'failed_count' => $failed,
            'manual_needed_count' => $manual,
            'results' => $results,
        );
    }

    private function aysa_technical_site_audit_apply_internal_linking_package($project_id, $package) {
        $package = is_array($package) ? $package : array();
        $candidates = isset($package['candidates']) && is_array($package['candidates']) ? $package['candidates'] : array();
        if (!class_exists('Aysa_Internal_Linking')) {
            return array('shared_executor' => 'Aysa_Internal_Linking::process_site_audit_scoped_targets', 'applied_count' => 0, 'skipped_count' => count($candidates), 'failed_count' => 0, 'manual_needed_count' => count($candidates), 'reason' => 'internal_linking_engine_unavailable', 'results' => array(), 'global_mode_used' => false);
        }
        $engine = new Aysa_Internal_Linking();
        if (!method_exists($engine, 'process_site_audit_scoped_targets')) {
            return array('shared_executor' => 'Aysa_Internal_Linking::process_site_audit_scoped_targets', 'applied_count' => 0, 'skipped_count' => count($candidates), 'failed_count' => 0, 'manual_needed_count' => count($candidates), 'reason' => 'scoped_internal_linking_apply_method_unavailable', 'results' => array(), 'global_mode_used' => false);
        }
        $result = $engine->process_site_audit_scoped_targets($package, false);
        $result = is_array($result) ? $result : array();
        return array_merge(array(
            'shared_executor' => 'Aysa_Internal_Linking::process_site_audit_scoped_targets',
            'applied_count' => 0,
            'skipped_count' => 0,
            'failed_count' => 0,
            'manual_needed_count' => 0,
            'results' => array(),
            'global_mode_used' => false,
            'reason' => '',
        ), $result);
    }

    private function aysa_technical_site_audit_apply_image_alt_package($project_id, $package) {
        $package = is_array($package) ? $package : array();
        $results = array();
        $applied = 0;
        $skipped = 0;
        $failed = 0;
        $manual = 0;
        $webp_converted = 0;
        $webp_skipped = 0;
        $seo_renamed = 0;
        $preserved_alt_optimized = 0;
        foreach ((array) ($package['items'] ?? array()) as $row) {
            if (!is_array($row)) {
                continue;
            }
            $attachment_id = intval($row['attachment_id'] ?? 0);
            $url = esc_url_raw((string) ($row['image_url'] ?? ''));
            $decision = sanitize_key((string) ($row['decision'] ?? $row['overwrite_decision'] ?? 'rewrite'));
            $confidence = intval($row['confidence'] ?? 0);
            $proposed = sanitize_text_field((string) ($row['proposed_value'] ?? ''));
            if ($attachment_id > 0 && !isset($row['decision']) && !isset($row['overwrite_decision']) && !isset($row['proposed_value'])) {
                $media_result = $this->aysa_technical_site_audit_optimize_image_attachment($project_id, $attachment_id, $row);
                if (!empty($media_result['webp_converted'])) {
                    $webp_converted++;
                } else {
                    $webp_skipped++;
                }
                if (!empty($media_result['seo_renamed'])) {
                    $seo_renamed++;
                }
                $preserved_alt_optimized++;
                $skipped++;
                $results[] = array_merge($row, array('status' => 'skipped', 'reason' => 'image_alt_preserved', 'image_optimization' => $media_result));
                continue;
            }
            if ($decision === 'preserve') {
                if ($attachment_id <= 0) {
                    $skipped++;
                    $results[] = array_merge($row, array('status' => 'skipped', 'reason' => 'attachment_mapping_missing'));
                    continue;
                }
                $media_result = !empty($row['media_optimization_eligible'])
                    ? $this->aysa_technical_site_audit_optimize_image_attachment($project_id, $attachment_id, $row)
                    : array('status' => 'skipped', 'reason' => 'image_alt_preserved');
                if (!empty($media_result['webp_converted'])) {
                    $webp_converted++;
                } else {
                    $webp_skipped++;
                }
                if (!empty($media_result['seo_renamed'])) {
                    $seo_renamed++;
                }
                $preserved_alt_optimized++;
                $skipped++;
                $results[] = array_merge($row, array('status' => 'skipped', 'reason' => 'image_alt_preserved', 'image_optimization' => $media_result));
                continue;
            }
            if ($decision !== 'rewrite') {
                $manual++;
                $results[] = array_merge($row, array('status' => 'manual_needed', 'reason' => 'image_alt_not_marked_for_rewrite'));
                continue;
            }
            if ($attachment_id <= 0) {
                $skipped++;
                $results[] = array_merge($row, array('status' => 'skipped', 'reason' => 'attachment_mapping_missing'));
                continue;
            }
            if (!empty($row['is_decorative'])) {
                $manual++;
                $results[] = array_merge($row, array('status' => 'manual_needed', 'reason' => 'decorative_image_not_auto_written'));
                continue;
            }
            if ($confidence < 70) {
                $media_result = !empty($row['media_optimization_eligible'])
                    ? $this->aysa_technical_site_audit_optimize_image_attachment($project_id, $attachment_id, $row)
                    : array('status' => 'skipped', 'reason' => 'image_alt_confidence_below_auto_apply_threshold');
                if (!empty($media_result['webp_converted'])) {
                    $webp_converted++;
                } else {
                    $webp_skipped++;
                }
                if (!empty($media_result['seo_renamed'])) {
                    $seo_renamed++;
                }
                $manual++;
                $results[] = array_merge($row, array('status' => 'manual_needed', 'reason' => 'image_alt_confidence_below_auto_apply_threshold', 'image_optimization' => $media_result));
                continue;
            }
            if ($proposed === '') {
                $media_result = !empty($row['media_optimization_eligible'])
                    ? $this->aysa_technical_site_audit_optimize_image_attachment($project_id, $attachment_id, $row)
                    : array('status' => 'skipped', 'reason' => 'empty_proposed_alt');
                if (!empty($media_result['webp_converted'])) {
                    $webp_converted++;
                } else {
                    $webp_skipped++;
                }
                if (!empty($media_result['seo_renamed'])) {
                    $seo_renamed++;
                }
                $skipped++;
                $results[] = array_merge($row, array('status' => 'skipped', 'reason' => 'empty_proposed_alt', 'image_optimization' => $media_result));
                continue;
            }
            $current = trim((string) get_post_meta($attachment_id, '_wp_attachment_image_alt', true));
            if ($current !== '' && $current === $proposed) {
                $applied++;
                $media_result = $this->aysa_technical_site_audit_optimize_image_attachment($project_id, $attachment_id, $row);
                if (!empty($media_result['webp_converted'])) {
                    $webp_converted++;
                } else {
                    $webp_skipped++;
                }
                if (!empty($media_result['seo_renamed'])) {
                    $seo_renamed++;
                }
                $results[] = array_merge($row, array('status' => 'already_present', 'current_value' => $current, 'image_optimization' => $media_result));
                continue;
            }
            if ($current !== '' && $current !== trim((string) ($row['old_value'] ?? ''))) {
                $media_result = !empty($row['media_optimization_eligible'])
                    ? $this->aysa_technical_site_audit_optimize_image_attachment($project_id, $attachment_id, $row)
                    : array('status' => 'skipped', 'reason' => 'existing_alt_changed_since_prepare');
                if (!empty($media_result['webp_converted'])) {
                    $webp_converted++;
                } else {
                    $webp_skipped++;
                }
                if (!empty($media_result['seo_renamed'])) {
                    $seo_renamed++;
                }
                $skipped++;
                $results[] = array_merge($row, array('status' => 'skipped', 'reason' => 'existing_alt_changed_since_prepare', 'current_value' => $current, 'image_optimization' => $media_result));
                continue;
            }
            $updated = update_post_meta($attachment_id, '_wp_attachment_image_alt', $proposed);
            if ($updated === false && trim((string) get_post_meta($attachment_id, '_wp_attachment_image_alt', true)) !== $proposed) {
                $failed++;
                $results[] = array_merge($row, array('status' => 'failed', 'reason' => 'attachment_alt_write_failed'));
            } else {
                update_post_meta($attachment_id, '_aysa_image_alt_generated_at', current_time('mysql'));
                update_post_meta($attachment_id, '_aysa_image_alt_source', 'technical_seo_image_optimization');
                update_post_meta($attachment_id, '_aysa_image_alt_keyword', sanitize_text_field((string) ($row['primary_keyword'] ?? '')));
                update_post_meta($attachment_id, '_aysa_image_alt_context_url', esc_url_raw((string) ($row['page_url'] ?? '')));
                update_post_meta($attachment_id, '_aysa_image_alt_hash', hash('sha256', wp_json_encode(array(
                    'attachment_id' => $attachment_id,
                    'alt' => $proposed,
                    'keyword' => (string) ($row['primary_keyword'] ?? ''),
                    'page_url' => (string) ($row['page_url'] ?? ''),
                ))));
                $media_result = $this->aysa_technical_site_audit_optimize_image_attachment($project_id, $attachment_id, $row);
                if (!empty($media_result['webp_converted'])) {
                    $webp_converted++;
                } else {
                    $webp_skipped++;
                }
                if (!empty($media_result['seo_renamed'])) {
                    $seo_renamed++;
                }
                $applied++;
                $results[] = array_merge($row, array('status' => 'success', 'current_value' => $proposed, 'shared_executor' => 'wordpress_media_alt_text_adapter', 'image_optimization' => $media_result));
            }
        }
        foreach ((array) ($package['skipped'] ?? array()) as $row) {
            if (!is_array($row)) {
                continue;
            }
            $decision = sanitize_key((string) ($row['decision'] ?? ($row['candidate_quality']['decision'] ?? 'manual_review')));
            if (in_array($decision, array('manual_review', 'decorative_ignore'), true)) {
                $attachment_id = intval($row['attachment_id'] ?? 0);
                $media_result = !empty($row['media_optimization_eligible']) && $attachment_id > 0
                    ? $this->aysa_technical_site_audit_optimize_image_attachment($project_id, $attachment_id, $row)
                    : array('status' => 'skipped', 'reason' => 'image_alt_review_needed');
                if (!empty($media_result['webp_converted'])) {
                    $webp_converted++;
                } else {
                    $webp_skipped++;
                }
                if (!empty($media_result['seo_renamed'])) {
                    $seo_renamed++;
                }
                $manual++;
                $results[] = array_merge($row, array('status' => 'manual_needed', 'reason' => sanitize_key((string) ($row['reason'] ?? 'image_alt_review_needed')), 'image_optimization' => $media_result));
            } else {
                $attachment_id = intval($row['attachment_id'] ?? 0);
                $media_result = $attachment_id > 0 ? $this->aysa_technical_site_audit_optimize_image_attachment($project_id, $attachment_id, $row) : array('status' => 'skipped', 'reason' => 'attachment_mapping_missing');
                if (!empty($media_result['webp_converted'])) {
                    $webp_converted++;
                } else {
                    $webp_skipped++;
                }
                if (!empty($media_result['seo_renamed'])) {
                    $seo_renamed++;
                }
                $preserved_alt_optimized++;
                $skipped++;
                $results[] = array_merge($row, array('status' => 'skipped', 'reason' => sanitize_key((string) ($row['reason'] ?? 'image_alt_preserved')), 'image_optimization' => $media_result));
            }
        }
        return array(
            'shared_executor' => 'wordpress_media_image_optimization_adapter',
            'executor_path' => 'Site Audit worker -> image alt package -> _wp_attachment_image_alt + AYSA WebP serving',
            'package_id' => sanitize_text_field((string) ($package['package_id'] ?? '')),
            'total_candidates' => intval($package['total_candidates'] ?? 0),
            'safe_auto_apply_count' => intval($package['safe_auto_apply_count'] ?? 0),
            'manual_review_needed_count' => intval($package['manual_review_needed_count'] ?? 0),
            'decorative_ignore_count' => intval($package['decorative_ignore_count'] ?? 0),
            'preserve_count' => intval($package['preserve_count'] ?? 0),
            'applied_count' => $applied,
            'skipped_count' => $skipped,
            'failed_count' => $failed,
            'manual_needed_count' => $manual,
            'webp_converted_count' => $webp_converted,
            'webp_skipped_count' => $webp_skipped,
            'seo_renamed_count' => $seo_renamed,
            'preserved_alt_optimized_count' => $preserved_alt_optimized,
            'results' => $results
        );
    }

    private function aysa_technical_site_audit_optimize_image_attachment($project_id, $attachment_id, $row) {
        global $wpdb;
        $project_id = intval($project_id);
        $attachment_id = intval($attachment_id);
        $row = is_array($row) ? $row : array();
        if ($attachment_id <= 0) {
            return array('status' => 'skipped', 'reason' => 'attachment_required');
        }
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $post_row = $wpdb->get_row($wpdb->prepare(
            "SELECT post_type, post_mime_type FROM {$wpdb->posts} WHERE ID = %d LIMIT 1",
            $attachment_id
        ), ARRAY_A);
        if (!is_array($post_row) || (string) ($post_row['post_type'] ?? '') !== 'attachment') {
            return array('status' => 'skipped', 'reason' => 'not_attachment');
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $attached_file = (string) $wpdb->get_var($wpdb->prepare(
            "SELECT meta_value FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = '_wp_attached_file' LIMIT 1",
            $attachment_id
        ));
        $uploads = wp_get_upload_dir();
        $base_dir = trailingslashit((string) ($uploads['basedir'] ?? ''));
        $file = $attached_file !== '' && preg_match('/^([A-Za-z]:)?[\/\\\\]/', $attached_file)
            ? $attached_file
            : $base_dir . ltrim($attached_file, '/');
        if (!is_string($file) || $file === '' || !file_exists($file) || !is_readable($file)) {
            return array('status' => 'skipped', 'reason' => 'attachment_file_missing');
        }

        $existing = get_post_meta($attachment_id, '_aysa_image_optimization', true);
        if (is_array($existing) && !empty($existing['webp_url']) && $this->aysa_image_optimization_webp_file_exists($existing)) {
            $promoted = $this->aysa_image_optimization_promote_webp_attachment($attachment_id, $existing);
            return array_merge($existing, array('status' => 'already_optimized', 'webp_converted' => false, 'seo_renamed' => false, 'metadata_promoted' => !empty($promoted['metadata_promoted']), 'references_updated' => intval($promoted['references_updated'] ?? 0)));
        }

        $mime = (string) ($post_row['post_mime_type'] ?? '');
        if (!in_array($mime, array('image/jpeg', 'image/png'), true)) {
            return array('status' => 'skipped', 'reason' => 'unsupported_mime_type', 'mime_type' => sanitize_text_field($mime));
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $metadata_raw = (string) $wpdb->get_var($wpdb->prepare(
            "SELECT meta_value FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = '_wp_attachment_metadata' LIMIT 1",
            $attachment_id
        ));
        $metadata = maybe_unserialize($metadata_raw);
        $metadata = is_array($metadata) ? $metadata : array();
        if (!get_post_meta($attachment_id, '_aysa_image_optimization_backup', true)) {
            update_post_meta($attachment_id, '_aysa_image_optimization_backup', array(
                'attached_file' => $attached_file,
                'metadata' => $metadata,
                'mime_type' => $mime,
                'created_at' => current_time('mysql'),
            ));
        }

        $keyword = trim((string) ($row['primary_keyword'] ?? ''));
        $secondary = isset($row['secondary_keywords']) && is_array($row['secondary_keywords']) ? array_values($row['secondary_keywords']) : array();
        if ($keyword === '') {
            $inventory = $this->aysa_image_optimization_get_inventory($project_id);
            $context = isset($inventory[$attachment_id]) && is_array($inventory[$attachment_id]) ? $inventory[$attachment_id] : array();
            $keyword = trim((string) ($context['primary_keyword'] ?? ''));
            $secondary = isset($context['secondary_keywords']) && is_array($context['secondary_keywords']) ? array_values($context['secondary_keywords']) : $secondary;
            if ($keyword === '') {
                $keyword = trim((string) ($context['page_title'] ?? get_the_title($attachment_id)));
            }
        }
        $slug = $this->aysa_image_optimization_slug($keyword, $secondary, $attachment_id);
        if ($slug === '') {
            return array('status' => 'skipped', 'reason' => 'keyword_context_missing');
        }

        $dir = dirname($file);
        $full_target = $this->aysa_image_optimization_unique_path($dir, $slug . '.webp');
        $full_result = $this->aysa_image_optimization_convert_to_webp($file, $full_target, $mime);
        if (empty($full_result['ok'])) {
            return array('status' => 'skipped', 'reason' => sanitize_key((string) ($full_result['reason'] ?? 'webp_conversion_failed')));
        }

        $base_dir = trailingslashit((string) ($uploads['basedir'] ?? ''));
        $base_url = trailingslashit((string) ($uploads['baseurl'] ?? ''));
        $full_rel = ltrim(str_replace($base_dir, '', $full_target), '/');
        $full_url = esc_url_raw($base_url . str_replace(DIRECTORY_SEPARATOR, '/', $full_rel));
        $original_url = $attached_file !== '' ? esc_url_raw($base_url . str_replace(DIRECTORY_SEPARATOR, '/', ltrim($attached_file, '/'))) : '';

        $sources = array(
            'full' => array(
                'old_url' => $original_url ? esc_url_raw((string) $original_url) : '',
                'webp_url' => $full_url,
                'webp_path' => $full_target,
                'original_path' => $file,
                'original_bytes' => filesize($file),
                'webp_bytes' => filesize($full_target),
            ),
        );

        $metadata_file = (string) ($metadata['file'] ?? '');
        $sizes = isset($metadata['sizes']) && is_array($metadata['sizes']) ? $metadata['sizes'] : array();
        foreach ($sizes as $size_key => $size_data) {
            if (!is_array($size_data) || empty($size_data['file'])) {
                continue;
            }
            $size_file = trailingslashit($dir) . basename((string) $size_data['file']);
            if (!file_exists($size_file) || !is_readable($size_file)) {
                continue;
            }
            $size_slug = $slug . '-' . sanitize_key((string) $size_key);
            $size_target = $this->aysa_image_optimization_unique_path($dir, $size_slug . '.webp');
            $size_mime = (string) ($size_data['mime-type'] ?? $mime);
            $size_result = $this->aysa_image_optimization_convert_to_webp($size_file, $size_target, $size_mime);
            if (empty($size_result['ok'])) {
                continue;
            }
            $size_rel = ltrim(str_replace($base_dir, '', $size_target), '/');
            $old_size_url = $metadata_file !== ''
                ? esc_url_raw($base_url . trailingslashit(dirname($metadata_file)) . basename((string) $size_data['file']))
                : '';
            $sources[sanitize_key((string) $size_key)] = array(
                'old_url' => $old_size_url,
                'webp_url' => esc_url_raw($base_url . str_replace(DIRECTORY_SEPARATOR, '/', $size_rel)),
                'webp_path' => $size_target,
                'original_path' => $size_file,
                'original_bytes' => filesize($size_file),
                'webp_bytes' => filesize($size_target),
            );
        }

        $optimization = array(
            'status' => 'optimized',
            'project_id' => $project_id,
            'attachment_id' => $attachment_id,
            'keyword' => sanitize_text_field($keyword),
            'secondary_keywords' => array_values(array_map('sanitize_text_field', $secondary)),
            'page_url' => esc_url_raw((string) ($row['page_url'] ?? '')),
            'seo_slug' => sanitize_title($slug),
            'old_filename' => basename($file),
            'webp_filename' => basename($full_target),
            'original_url' => $original_url ? esc_url_raw((string) $original_url) : '',
            'webp_url' => $full_url,
            'webp_path' => $full_target,
            'sources' => $sources,
            'generated_at' => current_time('mysql'),
            'webp_converted' => true,
            'seo_renamed' => basename($full_target) !== basename($file),
        );
        update_post_meta($attachment_id, '_aysa_image_optimization', $optimization);
        update_post_meta($attachment_id, '_aysa_image_webp_generated_at', current_time('mysql'));
        $this->aysa_image_optimization_register_url_map($project_id, $sources);
        $promoted = $this->aysa_image_optimization_promote_webp_attachment($attachment_id, $optimization);
        $optimization['metadata_promoted'] = !empty($promoted['metadata_promoted']);
        $optimization['references_updated'] = intval($promoted['references_updated'] ?? 0);
        update_post_meta($attachment_id, '_aysa_image_optimization', $optimization);

        return $optimization;
    }

    private function aysa_image_optimization_convert_to_webp($source, $target, $mime) {
        $source = (string) $source;
        $target = (string) $target;
        $mime = (string) $mime;
        if ($source === '' || $target === '' || !file_exists($source)) {
            return array('ok' => false, 'reason' => 'source_missing');
        }
        if (file_exists($target) && filesize($target) > 0) {
            return array('ok' => true, 'reason' => 'target_exists');
        }
        $imagick_reason = '';
        if (class_exists('Imagick')) {
            try {
                $image = new Imagick($source);
                $image->setImageFormat('webp');
                $image->setImageCompressionQuality(82);
                $image->stripImage();
                $image->writeImage($target);
                $image->clear();
                $image->destroy();
            } catch (Throwable $e) {
                $imagick_reason = 'imagick_webp_failed';
            }
        }
        if (!file_exists($target) || filesize($target) <= 0) {
            if (!function_exists('imagewebp')) {
                return array('ok' => false, 'reason' => $imagick_reason !== '' ? $imagick_reason : 'webp_support_missing');
            }
            $resource = null;
            if ($mime === 'image/png' && function_exists('imagecreatefrompng')) {
                $resource = @imagecreatefrompng($source);
                if ($resource) {
                    imagepalettetotruecolor($resource);
                    imagealphablending($resource, true);
                    imagesavealpha($resource, true);
                }
            } elseif ($mime === 'image/jpeg' && function_exists('imagecreatefromjpeg')) {
                $resource = @imagecreatefromjpeg($source);
            }
            if (!$resource) {
                return array('ok' => false, 'reason' => $imagick_reason !== '' ? 'imagick_failed_gd_image_load_failed' : 'gd_image_load_failed');
            }
            $written = @imagewebp($resource, $target, 82);
            imagedestroy($resource);
            if (!$written) {
                return array('ok' => false, 'reason' => $imagick_reason !== '' ? 'imagick_failed_gd_webp_write_failed' : 'gd_webp_write_failed');
            }
        }
        if (!file_exists($target) || filesize($target) <= 0) {
            return array('ok' => false, 'reason' => 'webp_file_invalid');
        }
        if (filesize($target) >= filesize($source)) {
            wp_delete_file($target);
            return array('ok' => false, 'reason' => 'webp_not_smaller');
        }
        return array('ok' => true);
    }

    private function aysa_image_optimization_slug($primary_keyword, $secondary_keywords, $attachment_id) {
        $parts = array(trim((string) $primary_keyword));
        foreach ((array) $secondary_keywords as $secondary) {
            $secondary = trim((string) $secondary);
            if ($secondary !== '') {
                $parts[] = $secondary;
                break;
            }
        }
        $slug = sanitize_title(remove_accents(implode(' ', array_filter($parts))));
        $slug = trim((string) preg_replace('/-+/', '-', $slug), '-');
        if ($slug === '') {
            return '';
        }
        if (strlen($slug) > 86) {
            $slug = substr($slug, 0, 86);
            $slug = trim((string) preg_replace('/-[^-]*$/', '', $slug), '-');
        }
        return $slug . '-' . substr(md5((string) intval($attachment_id)), 0, 6);
    }

    private function aysa_image_optimization_unique_path($dir, $filename) {
        $dir = untrailingslashit((string) $dir);
        $info = pathinfo((string) $filename);
        $base = sanitize_file_name((string) ($info['filename'] ?? 'aysa-image'));
        $ext = sanitize_key((string) ($info['extension'] ?? 'webp'));
        $path = trailingslashit($dir) . $base . '.' . $ext;
        $i = 2;
        while (file_exists($path)) {
            $path = trailingslashit($dir) . $base . '-' . $i . '.' . $ext;
            $i++;
        }
        return $path;
    }

    private function aysa_image_optimization_webp_file_exists($optimization) {
        $path = is_array($optimization) ? (string) ($optimization['webp_path'] ?? '') : '';
        return $path !== '' && file_exists($path);
    }

    private function aysa_image_optimization_register_url_map($project_id, $sources) {
        $map = get_option('aysa_image_optimization_url_map', array());
        $map = is_array($map) ? $map : array();
        foreach ((array) $sources as $source) {
            if (!is_array($source)) {
                continue;
            }
            $old = esc_url_raw((string) ($source['old_url'] ?? ''));
            $new = esc_url_raw((string) ($source['webp_url'] ?? ''));
            if ($old !== '' && $new !== '') {
                $map[$old] = array(
                    'webp_url' => $new,
                    'project_id' => intval($project_id),
                    'updated_at' => current_time('mysql'),
                );
            }
        }
        if (count($map) > 1000) {
            $map = array_slice($map, -1000, null, true);
        }
        update_option('aysa_image_optimization_url_map', $map, false);
    }

    private function aysa_image_optimization_promote_webp_attachment($attachment_id, $optimization) {
        global $wpdb;
        $attachment_id = intval($attachment_id);
        if ($attachment_id <= 0 || !is_array($optimization)) {
            return array('metadata_promoted' => false, 'references_updated' => 0);
        }
        $sources = is_array($optimization['sources'] ?? null) ? $optimization['sources'] : array();
        $full = is_array($sources['full'] ?? null) ? $sources['full'] : array();
        $webp_path = (string) ($full['webp_path'] ?? '');
        if ($webp_path === '' || !file_exists($webp_path)) {
            return array('metadata_promoted' => false, 'references_updated' => 0);
        }

        $uploads = wp_get_upload_dir();
        $base_dir = trailingslashit((string) ($uploads['basedir'] ?? ''));
        $webp_rel = ltrim(str_replace($base_dir, '', $webp_path), '/');
        if ($webp_rel === '' || $webp_rel === $webp_path) {
            return array('metadata_promoted' => false, 'references_updated' => 0);
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $metadata_raw = (string) $wpdb->get_var($wpdb->prepare(
            "SELECT meta_value FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = '_wp_attachment_metadata' LIMIT 1",
            $attachment_id
        ));
        $metadata = maybe_unserialize($metadata_raw);
        $metadata = is_array($metadata) ? $metadata : array();
        $metadata['file'] = str_replace(DIRECTORY_SEPARATOR, '/', $webp_rel);
        if (isset($metadata['filesize'])) {
            $metadata['filesize'] = filesize($webp_path);
        }
        $sizes = isset($metadata['sizes']) && is_array($metadata['sizes']) ? $metadata['sizes'] : array();
        foreach ($sizes as $size_key => $size_data) {
            $safe_key = sanitize_key((string) $size_key);
            $mapped = is_array($sources[$safe_key] ?? null) ? $sources[$safe_key] : array();
            $size_webp_path = (string) ($mapped['webp_path'] ?? '');
            if ($size_webp_path === '' || !file_exists($size_webp_path) || !is_array($size_data)) {
                continue;
            }
            $metadata['sizes'][$size_key]['file'] = basename($size_webp_path);
            $metadata['sizes'][$size_key]['mime-type'] = 'image/webp';
            if (isset($metadata['sizes'][$size_key]['filesize'])) {
                $metadata['sizes'][$size_key]['filesize'] = filesize($size_webp_path);
            }
        }

        update_post_meta($attachment_id, '_wp_attached_file', str_replace(DIRECTORY_SEPARATOR, '/', $webp_rel));
        update_post_meta($attachment_id, '_wp_attachment_metadata', $metadata);
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $wpdb->update(
            $wpdb->posts,
            array('post_mime_type' => 'image/webp'),
            array('ID' => $attachment_id),
            array('%s'),
            array('%d')
        );
        clean_post_cache($attachment_id);

        $updated = $this->aysa_image_optimization_replace_stored_references($sources);
        update_post_meta($attachment_id, '_aysa_image_optimization_promoted_at', current_time('mysql'));

        return array('metadata_promoted' => true, 'references_updated' => $updated);
    }

    private function aysa_image_optimization_replace_stored_references($sources) {
        global $wpdb;
        $pairs = array();
        foreach ((array) $sources as $source) {
            if (!is_array($source)) {
                continue;
            }
            $old = esc_url_raw((string) ($source['old_url'] ?? ''));
            $new = esc_url_raw((string) ($source['webp_url'] ?? ''));
            if ($old !== '' && $new !== '' && $old !== $new) {
                $pairs[$old] = $new;
            }
        }
        if ($pairs === array()) {
            return 0;
        }

        $updated = 0;
        foreach ($pairs as $old => $new) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $post_ids = $wpdb->get_col($wpdb->prepare("SELECT ID FROM {$wpdb->posts} WHERE post_content LIKE %s", '%' . $wpdb->esc_like($old) . '%'));
            foreach ((array) $post_ids as $post_id) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $content = (string) $wpdb->get_var($wpdb->prepare("SELECT post_content FROM {$wpdb->posts} WHERE ID = %d", intval($post_id)));
                $new_content = str_replace($old, $new, $content);
                if ($new_content !== $content) {
                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                    $wpdb->update($wpdb->posts, array('post_content' => $new_content), array('ID' => intval($post_id)), array('%s'), array('%d'));
                    clean_post_cache(intval($post_id));
                    $updated++;
                }
            }

            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $meta_rows = $wpdb->get_results($wpdb->prepare("SELECT meta_id, meta_value FROM {$wpdb->postmeta} WHERE meta_value LIKE %s", '%' . $wpdb->esc_like($old) . '%'), ARRAY_A);
            foreach ((array) $meta_rows as $meta_row) {
                $meta_id = intval($meta_row['meta_id'] ?? 0);
                $raw = (string) ($meta_row['meta_value'] ?? '');
                if ($meta_id <= 0 || $raw === '') {
                    continue;
                }
                $value = maybe_unserialize($raw);
                $new_value = $this->aysa_image_optimization_deep_replace($value, $old, $new);
                if ($new_value !== $value) {
                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                    $wpdb->update($wpdb->postmeta, array('meta_value' => maybe_serialize($new_value)), array('meta_id' => $meta_id), array('%s'), array('%d'));
                    $updated++;
                } elseif (is_string($value)) {
                    $new_raw = str_replace($old, $new, $raw);
                    if ($new_raw !== $raw) {
                        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                        $wpdb->update($wpdb->postmeta, array('meta_value' => $new_raw), array('meta_id' => $meta_id), array('%s'), array('%d'));
                        $updated++;
                    }
                }
            }
        }
        return $updated;
    }

    private function aysa_image_optimization_deep_replace($value, $old, $new) {
        if (is_string($value)) {
            return str_replace((string) $old, (string) $new, $value);
        }
        if (is_array($value)) {
            foreach ($value as $key => $item) {
                $value[$key] = $this->aysa_image_optimization_deep_replace($item, $old, $new);
            }
            return $value;
        }
        if (is_object($value)) {
            foreach (get_object_vars($value) as $key => $item) {
                $value->$key = $this->aysa_image_optimization_deep_replace($item, $old, $new);
            }
        }
        return $value;
    }

    public function filter_aysa_image_optimization_attachment_url($url, $attachment_id) {
        if (is_admin() || wp_doing_ajax()) {
            return $url;
        }
        $replacement = $this->aysa_image_optimization_replacement_for_attachment(intval($attachment_id), 'full');
        return $replacement !== '' ? $replacement : $url;
    }

    public function filter_aysa_image_optimization_attachment_src($image, $attachment_id, $size, $icon) {
        if (!is_array($image) || empty($image[0]) || is_admin() || wp_doing_ajax()) {
            return $image;
        }
        $size_key = is_array($size) ? 'full' : sanitize_key((string) $size);
        $replacement = $this->aysa_image_optimization_replacement_for_attachment(intval($attachment_id), $size_key);
        if ($replacement !== '') {
            $image[0] = $replacement;
        }
        return $image;
    }

    public function filter_aysa_image_optimization_srcset($sources, $size_array, $image_src, $image_meta, $attachment_id) {
        if (!is_array($sources) || is_admin() || wp_doing_ajax()) {
            return $sources;
        }
        $optimization = get_post_meta(intval($attachment_id), '_aysa_image_optimization', true);
        $source_map = is_array($optimization) && is_array($optimization['sources'] ?? null) ? $optimization['sources'] : array();
        if ($source_map === array()) {
            return $sources;
        }
        foreach ($sources as $width => $source) {
            if (!is_array($source) || empty($source['url'])) {
                continue;
            }
            foreach ($source_map as $mapped) {
                if (!is_array($mapped)) {
                    continue;
                }
                $old = esc_url_raw((string) ($mapped['old_url'] ?? ''));
                $new = esc_url_raw((string) ($mapped['webp_url'] ?? ''));
                if ($old !== '' && $new !== '' && $old === esc_url_raw((string) $source['url'])) {
                    $sources[$width]['url'] = $new;
                    break;
                }
            }
        }
        return $sources;
    }

    public function filter_aysa_image_optimization_content_urls($content) {
        if (!is_string($content) || $content === '' || is_admin()) {
            return $content;
        }
        $map = get_option('aysa_image_optimization_url_map', array());
        if (!is_array($map) || $map === array()) {
            return $content;
        }
        $search = array();
        $replace = array();
        foreach ($map as $old_url => $entry) {
            $new_url = is_array($entry) ? esc_url_raw((string) ($entry['webp_url'] ?? '')) : '';
            $old_url = esc_url_raw((string) $old_url);
            if ($old_url !== '' && $new_url !== '') {
                $search[] = $old_url;
                $replace[] = $new_url;
            }
        }
        return $search !== array() ? str_replace($search, $replace, $content) : $content;
    }

    private function aysa_image_optimization_replacement_for_attachment($attachment_id, $size_key = 'full') {
        $attachment_id = intval($attachment_id);
        if ($attachment_id <= 0) {
            return '';
        }
        $optimization = get_post_meta($attachment_id, '_aysa_image_optimization', true);
        if (!is_array($optimization)) {
            return '';
        }
        $sources = is_array($optimization['sources'] ?? null) ? $optimization['sources'] : array();
        $size_key = sanitize_key((string) $size_key);
        $candidate = is_array($sources[$size_key] ?? null) ? $sources[$size_key] : (is_array($sources['full'] ?? null) ? $sources['full'] : array());
        $path = (string) ($candidate['webp_path'] ?? '');
        $url = esc_url_raw((string) ($candidate['webp_url'] ?? ''));
        return $path !== '' && $url !== '' && file_exists($path) ? $url : '';
    }

    private function aysa_technical_site_audit_find_action_bundle($snapshot, $action_key) {
        $bundles = isset($snapshot['conclusion']['recommended_actions']) && is_array($snapshot['conclusion']['recommended_actions'])
            ? $snapshot['conclusion']['recommended_actions']
            : array();
        foreach ($bundles as $bundle) {
            if (is_array($bundle) && sanitize_key((string) ($bundle['action_key'] ?? '')) === sanitize_key((string) $action_key)) {
                return $bundle;
            }
        }
        $action_key = sanitize_key((string) $action_key);
        $state_bundles = isset($snapshot['action_bundles']) && is_array($snapshot['action_bundles'])
            ? $snapshot['action_bundles']
            : array();
        if (isset($state_bundles[$action_key]) && is_array($state_bundles[$action_key])) {
            return array_merge(array('action_key' => $action_key), $state_bundles[$action_key]);
        }
        foreach ($state_bundles as $bundle) {
            if (is_array($bundle) && sanitize_key((string) ($bundle['action_key'] ?? '')) === $action_key) {
                return $bundle;
            }
        }
        return array();
    }

    private function aysa_technical_site_audit_apply_metadata_remediation($project_id, $payload = array()) {
        $project_id = intval($project_id);
        $payload = is_array($payload) ? $payload : array();
        $snapshot = $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id);
        $bundle = $this->aysa_technical_site_audit_find_action_bundle($snapshot, 'prepare_metadata_remediation');
        if (empty($bundle)) {
            return array('applied' => false, 'reason' => 'metadata_bundle_not_available');
        }
        $package_id = sanitize_text_field((string) ($bundle['execution_package_id'] ?? ''));
        if ($package_id === '') {
            $package_id = 'site_audit_' . $project_id . '_prepare_metadata_remediation_' . substr(md5(current_time('mysql')), 0, 10);
        }
        $package = !empty($bundle['metadata_execution_package']) && is_array($bundle['metadata_execution_package'])
            ? $bundle['metadata_execution_package']
            : $this->aysa_technical_site_audit_build_metadata_execution_package($project_id, $snapshot, $bundle, $package_id);

        $results = array();
        $applied_count = 0;
        $skipped_count = 0;
        $failed_count = 0;
        $onpage_service = $this->aysa_onpage_workspace_service();
        if (!$onpage_service || !method_exists($onpage_service, 'apply_projection') || !method_exists($onpage_service, 'apply_meta_output')) {
            $this->aysa_agent8_create_site_audit_communication_event(
                $project_id,
                'site_audit_metadata_apply_failed',
                'Agent8 Site Audit: ' . $this->aysa_technical_site_audit_action_subject_label('prepare_metadata_remediation', $package) . ' blocked',
                "I tried to apply the Site Audit metadata package, but the shared On-Page metadata workspace is unavailable. I did not fall back to a local shortcut.",
                array('action_key' => 'prepare_metadata_remediation', 'reason' => 'onpage_workspace_service_unavailable'),
                'warning'
            );
            return array('applied' => false, 'reason' => 'onpage_workspace_service_unavailable');
        }
        foreach ((array) ($package['items'] ?? array()) as $item) {
            if (!is_array($item)) {
                continue;
            }
            $post_id = intval($item['post_id'] ?? 0);
            $field = sanitize_key((string) ($item['field'] ?? ''));
            $new_value = sanitize_text_field((string) ($item['proposed_value'] ?? ''));
            $old_value = sanitize_text_field((string) ($item['old_value'] ?? ''));
            $post = $post_id > 0 ? get_post($post_id) : null;
            $validation = $this->aysa_technical_site_audit_validate_metadata_proposal($field, $new_value);
            if ($post_id <= 0 || !$post || !$validation['ok']) {
                $skipped_count++;
                $results[] = array(
                    'url' => esc_url_raw((string) ($item['url'] ?? '')),
                    'post_id' => $post_id,
                    'field' => $field,
                    'old_value' => $old_value,
                    'new_value' => $new_value,
                    'status' => 'skipped',
                    'reason' => $post_id <= 0 ? 'missing_post_id' : (!$post ? 'post_not_found' : $validation['reason']),
                );
                continue;
            }
            if ($old_value === $new_value) {
                $skipped_count++;
                $results[] = array(
                    'url' => esc_url_raw((string) ($item['url'] ?? '')),
                    'post_id' => $post_id,
                    'field' => $field,
                    'old_value' => $old_value,
                    'new_value' => $new_value,
                    'status' => 'skipped',
                    'reason' => 'already_matches_proposal',
                );
                continue;
            }
            $entity_key = sanitize_text_field((string) ($item['entity_key'] ?? ('post:' . $post_id)));
            $projection_row = $this->aysa_technical_site_audit_build_onpage_metadata_projection_row(
                $project_id,
                $item,
                $post,
                intval($payload['worker_run_id'] ?? 0)
            );
            $projection = $onpage_service->apply_projection($project_id, intval($payload['worker_run_id'] ?? 0), array(
                'run_type' => 'technical_site_audit_metadata_remediation',
                'run_status' => 'completed',
                'current_task_key' => 'technical_site_audit_metadata_apply',
                'current_milestone' => 'Site Audit metadata remediation package synced to On-Page workspace',
                'inventory_rows' => array($projection_row),
                'summary' => array(
                    'source' => 'site_audit_metadata_remediation',
                    'package_id' => $package_id,
                    'target_field' => $field,
                ),
            ));
            $onpage_result = !empty($projection['ok'])
                ? $onpage_service->apply_meta_output($project_id, $entity_key)
                : array('ok' => false, 'error' => 'onpage_projection_sync_failed');
            if (!empty($onpage_result['ok'])) {
                $applied_count++;
                $results[] = array(
                    'url' => esc_url_raw((string) ($item['url'] ?? '')),
                    'post_id' => $post_id,
                    'entity_key' => $entity_key,
                    'field' => $field,
                    'old_value' => $old_value,
                    'new_value' => $new_value,
                    'status' => 'success',
                    'reason' => '',
                    'shared_metadata_path' => 'onpage_workspace_apply_meta_output',
                    'onpage_result' => $onpage_result,
                );
            } else {
                $failed_count++;
                $results[] = array(
                    'url' => esc_url_raw((string) ($item['url'] ?? '')),
                    'post_id' => $post_id,
                    'entity_key' => $entity_key,
                    'field' => $field,
                    'old_value' => $old_value,
                    'new_value' => $new_value,
                    'status' => 'failed',
                    'reason' => sanitize_text_field((string) ($onpage_result['error'] ?? 'onpage_metadata_apply_failed')),
                    'shared_metadata_path' => 'onpage_workspace_apply_meta_output',
                );
            }
        }
        $result = array(
            'result_type' => 'worker_applied_metadata_via_onpage_workspace',
            'worker_run_id' => intval($payload['worker_run_id'] ?? 0),
            'worker_task_id' => intval($payload['worker_task_id'] ?? 0),
            'automatic_site_mutation' => true,
            'shared_executor' => 'OnPageWorkspaceService::apply_meta_output',
            'executor_path' => 'Site Audit worker -> On-Page metadata workspace -> apply_meta_output',
            'shared_metadata_path' => 'onpage_workspace_apply_meta_output',
            'applied_count' => $applied_count,
            'skipped_count' => $skipped_count + intval($package['skipped_count'] ?? 0),
            'failed_count' => $failed_count,
            'manual_needed_count' => intval($package['skipped_count'] ?? 0),
            'results' => array_values($results),
            'package_skipped' => isset($package['skipped']) && is_array($package['skipped']) ? array_values($package['skipped']) : array(),
        );
        $result = $this->aysa_technical_site_audit_normalize_apply_result('prepare_metadata_remediation', $package_id, $result);
        $result['communication_event'] = $this->aysa_agent8_create_site_audit_communication_event(
            $project_id,
            $failed_count > 0 ? 'site_audit_metadata_apply_completed_with_warnings' : 'site_audit_metadata_apply_completed',
            'Agent8 Site Audit: ' . $this->aysa_technical_site_audit_action_subject_label('prepare_metadata_remediation', $package) . ' completed',
            sprintf(
                "I finished the metadata batch.\n\nApplied: %d pages\nSkipped: %d pages\nFailed: %d pages\n\nThe applied changes used the shared On-Page metadata path and were recorded per page. Skipped items need manual review or a missing shared mapping.",
                $applied_count,
                $skipped_count + intval($package['skipped_count'] ?? 0),
                $failed_count
            ),
            array(
                'action_key' => 'prepare_metadata_remediation',
                'package_id' => $package_id,
                'shared_metadata_path' => 'onpage_workspace_apply_meta_output',
                'applied_count' => $applied_count,
                'skipped_count' => $skipped_count + intval($package['skipped_count'] ?? 0),
                'failed_count' => $failed_count,
            ),
            $failed_count > 0 ? 'warning' : 'info'
        );
        $state = $failed_count > 0 && $applied_count <= 0 ? 'failed' : 'applied';
        $this->aysa_adjustments_update_site_audit_action_bundle_state($project_id, 'prepare_metadata_remediation', array(
            'approval_state' => $state,
            'execution_state' => $state,
            'execution_package_id' => $package_id,
            'metadata_execution_package' => $package,
            'last_applied_at' => current_time('mysql'),
            'last_apply_result' => $result,
            'last_error' => $state === 'failed' ? 'metadata_worker_apply_failed' : '',
        ));
        return array(
            'applied' => true,
            'stage' => 'apply',
            'execution_state' => $state,
            'action_key' => 'prepare_metadata_remediation',
            'execution_package_id' => $package_id,
            'applied_count' => $applied_count,
            'skipped_count' => $result['skipped_count'],
            'failed_count' => $failed_count,
            'apply_result' => $result,
        );
    }

    private function aysa_technical_site_audit_verify_metadata_remediation($project_id, $bundle, $payload = array()) {
        $project_id = intval($project_id);
        $bundle = is_array($bundle) ? $bundle : array();
        $apply_result = isset($bundle['last_apply_result']) && is_array($bundle['last_apply_result']) ? $bundle['last_apply_result'] : array();
        $verified = 0;
        $checked = 0;
        $rows = array();
        foreach ((array) ($apply_result['results'] ?? array()) as $row) {
            if (!is_array($row) || sanitize_key((string) ($row['status'] ?? '')) !== 'success') {
                continue;
            }
            $post_id = intval($row['post_id'] ?? 0);
            $field = sanitize_key((string) ($row['field'] ?? ''));
            $expected = sanitize_text_field((string) ($row['new_value'] ?? ''));
            if ($post_id <= 0 || $expected === '') {
                continue;
            }
            $checked++;
            $current = $field === 'meta_title'
                ? trim((string) get_post_meta($post_id, '_aysa_meta_title', true))
                : trim((string) get_post_meta($post_id, '_aysa_meta_description', true));
            $matches = $current === $expected;
            if ($matches) {
                $verified++;
            }
            $rows[] = array(
                'url' => esc_url_raw((string) ($row['url'] ?? '')),
                'post_id' => $post_id,
                'field' => $field,
                'expected_value' => $expected,
                'current_value' => sanitize_text_field($current),
                'verified' => $matches,
            );
        }
        $is_verified = $checked > 0 && $verified === $checked;
        $verify_result = $this->aysa_technical_site_audit_normalize_verification_result(array(
            'verification_method' => 'live_wordpress_metadata_readback',
            'checked_count' => $checked,
            'verified_count' => $verified,
            'verified' => $is_verified,
            'evidence' => $is_verified
                ? 'Live WordPress metadata readback matches all worker-applied metadata values.'
                : 'Live WordPress metadata readback does not yet confirm every applied metadata value.',
            'rows' => $rows,
        ));
        $verify_result['communication_event'] = $this->aysa_agent8_create_site_audit_communication_event(
            $project_id,
            $is_verified ? 'site_audit_metadata_verify_completed' : 'site_audit_metadata_verify_pending',
            $is_verified ? 'Agent8 Site Audit: ' . $this->aysa_technical_site_audit_action_subject_label('prepare_metadata_remediation', $bundle['metadata_execution_package'] ?? array()) . ' verified' : 'Agent8 Site Audit: ' . $this->aysa_technical_site_audit_action_subject_label('prepare_metadata_remediation', $bundle['metadata_execution_package'] ?? array()) . ' verification pending',
            $is_verified
                ? sprintf("I checked the live WordPress metadata and verified all %d applied metadata changes. The Site Audit metadata package is confirmed by live readback.", $checked)
                : sprintf("I checked the live WordPress metadata, but only %d of %d applied changes are confirmed. I will not mark this fully verified without evidence.", $verified, $checked),
            array(
                'action_key' => 'prepare_metadata_remediation',
                'checked_count' => $checked,
                'verified_count' => $verified,
                'verification_method' => 'live_wordpress_metadata_readback',
            ),
            $is_verified ? 'info' : 'warning'
        );
        $this->aysa_adjustments_update_site_audit_action_bundle_state($project_id, 'prepare_metadata_remediation', array(
            'execution_state' => $is_verified ? 'verified' : 'verification_pending',
            'approval_state' => $is_verified ? 'verified' : 'verification_pending',
            'last_verified_at' => current_time('mysql'),
            'last_verify_result' => $verify_result,
            'last_error' => '',
        ));
        return array(
            'applied' => true,
            'stage' => 'verify',
            'execution_state' => $is_verified ? 'verified' : 'verification_pending',
            'action_key' => 'prepare_metadata_remediation',
            'execution_package_id' => sanitize_text_field((string) ($bundle['execution_package_id'] ?? '')),
            'verification' => $verify_result,
        );
    }

    private function aysa_technical_site_audit_verify_response_code_review($project_id, $bundle, $payload = array()) {
        $project_id = intval($project_id);
        $bundle = is_array($bundle) ? $bundle : array();
        $apply_result = isset($bundle['last_apply_result']) && is_array($bundle['last_apply_result']) ? $bundle['last_apply_result'] : array();
        $checked = 0;
        $verified = 0;
        $rows = array();
        foreach ((array) ($apply_result['results'] ?? array()) as $row) {
            if (!is_array($row) || !in_array(sanitize_key((string) ($row['status'] ?? '')), array('success', 'already_present'), true)) {
                continue;
            }
            $source = esc_url_raw((string) ($row['source_url'] ?? ''));
            $target = esc_url_raw((string) ($row['proposed_target_url'] ?? ''));
            if ($source === '' || $target === '') {
                continue;
            }
            $checked++;
            $exists = $this->aysa_technical_site_audit_redirect_rule_exists($source, $target);
            if ($exists) {
                $verified++;
            }
            $rows[] = array(
                'source_url' => $source,
                'target_url' => $target,
                'verified' => $exists,
                'verification_method' => 'htaccess_redirect_rule_readback',
            );
        }
        $is_verified = $checked > 0 && $verified === $checked;
        $verify_result = $this->aysa_technical_site_audit_normalize_verification_result(array(
            'verification_method' => 'htaccess_redirect_rule_readback',
            'checked_count' => $checked,
            'verified_count' => $verified,
            'verified' => $is_verified,
            'evidence' => $is_verified
                ? 'The applied redirect rules are present in the shared redirect executor storage.'
                : 'No complete redirect readback evidence exists yet; not marking this package verified.',
            'rows' => $rows,
        ));
        if (empty($payload['suppress_plugin_communication'])) {
            $verify_result['communication_event'] = $this->aysa_agent8_create_site_audit_communication_event(
                $project_id,
                $is_verified ? 'site_audit_redirect_verify_completed' : 'site_audit_redirect_verify_pending',
                $is_verified ? 'Agent8 Site Audit: Response codes & redirects verified' : 'Agent8 Site Audit: Response codes & redirects verification pending',
                $is_verified
                    ? sprintf("I checked the redirect rules and verified all %d applied redirect changes. The Site Audit response-code package is confirmed by redirect readback.", $checked)
                    : sprintf("I checked the redirect rules, but only %d of %d applied redirects are confirmed. I will not mark this fully verified without evidence.", $verified, $checked),
                array(
                    'action_key' => 'prepare_response_code_review',
                    'checked_count' => $checked,
                    'verified_count' => $verified,
                    'verification_method' => 'htaccess_redirect_rule_readback',
                ),
                $is_verified ? 'info' : 'warning'
            );
        } else {
            $verify_result['communication_event'] = array('ok' => true, 'source' => 'suppressed_for_api_worker_shared_service');
        }
        $this->aysa_adjustments_update_site_audit_action_bundle_state($project_id, 'prepare_response_code_review', array(
            'execution_state' => $is_verified ? 'verified' : 'verification_pending',
            'approval_state' => $is_verified ? 'verified' : 'verification_pending',
            'last_verified_at' => current_time('mysql'),
            'last_verify_result' => $verify_result,
            'last_error' => '',
        ));
        return array(
            'applied' => true,
            'stage' => 'verify',
            'execution_state' => $is_verified ? 'verified' : 'verification_pending',
            'action_key' => 'prepare_response_code_review',
            'execution_package_id' => sanitize_text_field((string) ($bundle['execution_package_id'] ?? '')),
            'verification' => $verify_result,
        );
    }

    private function aysa_technical_site_audit_normalize_image_alt_execution_package($package, $project_id, $payload = array()) {
        $project_id = intval($project_id);
        $payload = is_array($payload) ? $payload : array();
        if (!is_array($package)) {
            return array('ok' => false, 'reason' => 'invalid_items_schema');
        }

        $package_id = sanitize_text_field((string) ($package['package_id'] ?? ''));
        if ($package_id === '') {
            return array('ok' => false, 'reason' => 'missing_package_id');
        }

        $package_project_id = intval($package['project_id'] ?? $project_id);
        if ($project_id <= 0 || $package_project_id <= 0 || $package_project_id !== $project_id) {
            return array('ok' => false, 'reason' => 'invalid_project_binding');
        }

        $items = isset($package['items']) && is_array($package['items']) ? array_values($package['items']) : array();
        $skipped = isset($package['skipped']) && is_array($package['skipped']) ? array_values($package['skipped']) : array();
        foreach ($items as $index => $row) {
            if (!is_array($row)) {
                return array('ok' => false, 'reason' => 'invalid_items_schema', 'row_index' => intval($index));
            }
            $proposed = trim((string) ($row['proposed_alt'] ?? $row['proposed_value'] ?? $row['new_alt'] ?? ''));
            if ($proposed === '') {
                return array('ok' => false, 'reason' => 'invalid_items_schema', 'row_index' => intval($index), 'field' => 'proposed_alt');
            }
            $attachment_id = intval($row['attachment_id'] ?? $row['media_id'] ?? 0);
            $row_id = sanitize_text_field((string) ($row['row_id'] ?? $row['item_id'] ?? ''));
            if ($row_id === '') {
                $row_id = $attachment_id > 0 ? 'attachment:' . $attachment_id : 'image_alt:' . substr(md5(wp_json_encode($row)), 0, 12);
            }
            $current = sanitize_text_field((string) ($row['current_alt'] ?? $row['current_value'] ?? $row['old_alt'] ?? $row['old_value'] ?? $row['existing_alt'] ?? ''));
            $items[$index] = array_merge($row, array(
                'row_id' => $row_id,
                'item_id' => sanitize_text_field((string) ($row['item_id'] ?? $row_id)),
                'attachment_id' => $attachment_id,
                'media_id' => intval($row['media_id'] ?? $attachment_id),
                'image_url' => esc_url_raw((string) ($row['image_url'] ?? '')),
                'source_page_url' => esc_url_raw((string) ($row['source_page_url'] ?? $row['page_url'] ?? '')),
                'page_url' => esc_url_raw((string) ($row['page_url'] ?? $row['source_page_url'] ?? '')),
                'source_post_id' => intval($row['source_post_id'] ?? $row['post_id'] ?? 0),
                'current_alt' => $current,
                'current_value' => $current,
                'old_alt' => sanitize_text_field((string) ($row['old_alt'] ?? $row['old_value'] ?? $current)),
                'old_value' => sanitize_text_field((string) ($row['old_value'] ?? $row['old_alt'] ?? $current)),
                'proposed_alt' => sanitize_text_field($proposed),
                'proposed_value' => sanitize_text_field((string) ($row['proposed_value'] ?? $proposed)),
                'classification' => sanitize_key((string) ($row['classification'] ?? 'safe_auto_apply')),
                'confidence' => max(0, min(100, intval($row['confidence'] ?? 0))),
                'risk' => sanitize_key((string) ($row['risk'] ?? 'low')),
                'write_target' => sanitize_text_field((string) ($row['write_target'] ?? '_wp_attachment_image_alt')),
                'idempotency_context' => is_array($row['idempotency_context'] ?? null) ? $row['idempotency_context'] : array(
                    'action_key' => 'prepare_image_alt_text',
                    'attachment_id' => $attachment_id,
                    'proposed_hash' => substr(hash('sha256', $proposed), 0, 16),
                ),
                'backup_required' => array_key_exists('backup_required', $row) ? !empty($row['backup_required']) : true,
                'readback_required' => array_key_exists('readback_required', $row) ? !empty($row['readback_required']) : true,
            ));
        }
        foreach ($skipped as $index => $row) {
            if (!is_array($row)) {
                return array('ok' => false, 'reason' => 'invalid_items_schema', 'row_index' => intval($index), 'bucket' => 'skipped');
            }
            $skipped[$index] = array_merge($row, array(
                'attachment_id' => intval($row['attachment_id'] ?? $row['media_id'] ?? 0),
                'media_id' => intval($row['media_id'] ?? $row['attachment_id'] ?? 0),
                'image_url' => esc_url_raw((string) ($row['image_url'] ?? '')),
                'source_page_url' => esc_url_raw((string) ($row['source_page_url'] ?? $row['page_url'] ?? '')),
                'page_url' => esc_url_raw((string) ($row['page_url'] ?? $row['source_page_url'] ?? '')),
                'current_alt' => sanitize_text_field((string) ($row['current_alt'] ?? $row['current_value'] ?? $row['old_alt'] ?? $row['old_value'] ?? $row['existing_alt'] ?? '')),
                'proposed_alt' => sanitize_text_field((string) ($row['proposed_alt'] ?? $row['proposed_value'] ?? '')),
            ));
        }

        $package['package_id'] = $package_id;
        $package['project_id'] = $project_id;
        $package['website_id'] = intval($package['website_id'] ?? $payload['website_id'] ?? 0);
        $package['run_id'] = intval($package['run_id'] ?? $payload['worker_run_id'] ?? 0);
        $package['task_id'] = intval($package['task_id'] ?? $payload['worker_task_id'] ?? 0);
        $package['generated_at'] = sanitize_text_field((string) ($package['generated_at'] ?? $package['created_at'] ?? current_time('mysql')));
        $package['created_at'] = sanitize_text_field((string) ($package['created_at'] ?? $package['generated_at']));
        $package['action_key'] = 'prepare_image_alt_text';
        $package['issue_family'] = 'image_alt';
        $package['items'] = $items;
        $package['candidates'] = $items;
        $package['skipped'] = $skipped;
        $package['total_candidates'] = intval($package['total_candidates'] ?? (count($items) + count($skipped)));
        $package['safe_auto_apply_count'] = intval($package['safe_auto_apply_count'] ?? count($items));
        $package['manual_review_needed_count'] = intval($package['manual_review_needed_count'] ?? 0);
        $package['preserve_count'] = intval($package['preserve_count'] ?? 0);
        $package['failed_count'] = intval($package['failed_count'] ?? 0);
        $package['no_apply'] = true;
        $package['no_wordpress_write'] = true;
        $package['writes_enabled'] = false;
        $package['apply_allowed'] = false;
        if (isset($payload['billing']) && is_array($payload['billing'])) {
            $package['billing'] = $payload['billing'];
        }

        return array('ok' => true, 'package' => $package);
    }

    private function aysa_technical_site_audit_update_action_lifecycle($project_id, $action_key, $stage, $payload = array()) {
        $project_id = intval($project_id);
        $action_key = sanitize_key((string) $action_key);
        $stage = sanitize_key((string) $stage);
        $payload = is_array($payload) ? $payload : array();
        if ($project_id <= 0 || $action_key === '' || !$this->aysa_adjustments_is_site_audit_bundle_action_key($action_key)) {
            return array('applied' => false, 'reason' => 'invalid_action_context');
        }
        if (!in_array($stage, array('prepare', 'apply', 'verify', 'ignore'), true)) {
            return array('applied' => false, 'reason' => 'invalid_action_stage');
        }

        $snapshot = $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id);
        $bundle = $this->aysa_technical_site_audit_find_action_bundle($snapshot, $action_key);
        $payload_image_alt_package = isset($payload['image_alt_execution_package']) && is_array($payload['image_alt_execution_package'])
            ? $payload['image_alt_execution_package']
            : (isset($payload['execution_package']) && is_array($payload['execution_package']) ? $payload['execution_package'] : array());
        if (empty($bundle) && $stage === 'prepare' && $action_key === 'prepare_image_alt_text' && !empty($payload_image_alt_package)) {
            $bundle = array(
                'action_key' => 'prepare_image_alt_text',
                'bundle_type' => 'image_alt',
                'issue_family' => 'image_alt',
                'affected_count' => intval($payload_image_alt_package['total_candidates'] ?? count((array) ($payload_image_alt_package['items'] ?? array()))),
                'execution_package_id' => sanitize_text_field((string) ($payload_image_alt_package['package_id'] ?? '')),
            );
        }
        if (empty($bundle)) {
            return array('applied' => false, 'reason' => 'action_bundle_not_available');
        }

        $now = current_time('mysql');
        $package_id = sanitize_text_field((string) ($bundle['execution_package_id'] ?? ''));
        if ($package_id === '') {
            $package_id = 'site_audit_' . $project_id . '_' . $action_key . '_' . substr(md5($now), 0, 10);
        }
        $affected_count = intval($bundle['affected_count'] ?? 0);
        $version_number = intval($snapshot['version_number'] ?? 0);
        $history_count = intval($snapshot['history_count'] ?? 0);

        if ($stage === 'prepare') {
            $package = array();
            if ($action_key === 'prepare_metadata_remediation') {
                $package = isset($payload['metadata_execution_package']) && is_array($payload['metadata_execution_package'])
                    ? $payload['metadata_execution_package']
                    : (isset($payload['execution_package']) && is_array($payload['execution_package'])
                        ? $payload['execution_package']
                        : $this->aysa_technical_site_audit_build_metadata_execution_package($project_id, $snapshot, $bundle, $package_id));
            } elseif ($action_key === 'prepare_response_code_review') {
                $package = isset($payload['redirect_execution_package']) && is_array($payload['redirect_execution_package'])
                    ? $payload['redirect_execution_package']
                    : (isset($payload['execution_package']) && is_array($payload['execution_package'])
                        ? $payload['execution_package']
                        : $this->aysa_technical_site_audit_build_redirect_execution_package($project_id, $snapshot, $bundle, $package_id));
            } elseif ($action_key === 'prepare_internal_linking_plan') {
                $package = isset($payload['internal_linking_execution_package']) && is_array($payload['internal_linking_execution_package'])
                    ? $payload['internal_linking_execution_package']
                    : (isset($payload['execution_package']) && is_array($payload['execution_package'])
                        ? $payload['execution_package']
                        : $this->aysa_technical_site_audit_build_internal_linking_execution_package($project_id, $snapshot, $bundle, $package_id));
            } elseif ($action_key === 'prepare_image_alt_text') {
                $package = isset($payload['image_alt_execution_package']) && is_array($payload['image_alt_execution_package'])
                    ? $payload['image_alt_execution_package']
                    : (isset($payload['execution_package']) && is_array($payload['execution_package'])
                        ? $payload['execution_package']
                        : array());
            } elseif ($action_key === 'prepare_indexability_review') {
                $package = isset($payload['canonical_execution_package']) && is_array($payload['canonical_execution_package'])
                    ? $payload['canonical_execution_package']
                    : (isset($payload['execution_package']) && is_array($payload['execution_package'])
                        ? $payload['execution_package']
                        : array());
            }
            if (!empty($package['package_id'])) {
                $package_id = sanitize_text_field((string) $package['package_id']);
            }
            if ($action_key === 'prepare_image_alt_text') {
                $package_validation = $this->aysa_technical_site_audit_normalize_image_alt_execution_package($package, $project_id, $payload);
                if (empty($package_validation['ok'])) {
                    return array(
                        'applied' => false,
                        'reason' => sanitize_key((string) ($package_validation['reason'] ?? 'invalid_items_schema')),
                        'validation' => $package_validation,
                    );
                }
                $package = isset($package_validation['package']) && is_array($package_validation['package']) ? $package_validation['package'] : $package;
                $package_id = sanitize_text_field((string) ($package['package_id'] ?? $package_id));
            }
            $prepare_result = array();
            if ($action_key === 'prepare_metadata_remediation') {
                $prepare_result = array(
                    'result_type' => 'metadata_execution_package_prepared',
                    'issue_family' => 'metadata',
                    'execution_package' => $package,
                    'total_candidates' => intval($package['total_candidates'] ?? 0),
                    'safe_auto_apply_count' => intval($package['safe_auto_apply_count'] ?? 0),
                    'manual_review_needed_count' => intval($package['manual_review_needed_count'] ?? 0),
                    'no_valid_target_count' => intval($package['no_valid_target_count'] ?? 0),
                    'package_items_count' => intval($package['items_count'] ?? 0),
                    'package_skipped_count' => intval($package['skipped_count'] ?? 0),
                    'automatic_site_mutation' => false,
                );
            } elseif ($action_key === 'prepare_response_code_review') {
                $prepare_result = array(
                    'result_type' => 'redirect_execution_package_prepared',
                    'issue_family' => 'response_codes_redirects',
                    'execution_package' => $package,
                    'redirect_execution_package' => $package,
                    'total_candidates' => intval($package['total_candidates'] ?? 0),
                    'safe_auto_apply_count' => intval($package['safe_auto_apply_count'] ?? 0),
                    'manual_review_needed_count' => intval($package['manual_review_needed_count'] ?? 0),
                    'no_valid_target_count' => intval($package['no_valid_target_count'] ?? 0),
                    'automatic_site_mutation' => false,
                );
            } elseif ($action_key === 'prepare_internal_linking_plan') {
                $prepare_result = array(
                    'result_type' => 'internal_linking_execution_package_prepared',
                    'issue_family' => 'internal_linking',
                    'execution_package' => $package,
                    'internal_linking_execution_package' => $package,
                    'total_candidates' => intval($package['total_candidates'] ?? 0),
                    'safe_auto_apply_count' => intval($package['safe_auto_apply_count'] ?? 0),
                    'manual_review_needed_count' => intval($package['manual_review_needed_count'] ?? 0),
                    'no_valid_target_count' => intval($package['no_valid_target_count'] ?? 0),
                    'automatic_site_mutation' => false,
                );
            } elseif ($action_key === 'prepare_image_alt_text') {
                $prepare_result = array(
                    'result_type' => 'image_alt_execution_package_prepared',
                    'issue_family' => 'image_alt',
                    'execution_package' => $package,
                    'image_alt_execution_package' => $package,
                    'total_candidates' => intval($package['total_candidates'] ?? 0),
                    'safe_auto_apply_count' => intval($package['safe_auto_apply_count'] ?? 0),
                    'manual_review_needed_count' => intval($package['manual_review_needed_count'] ?? 0),
                    'decorative_ignore_count' => intval($package['decorative_ignore_count'] ?? 0),
                    'preserve_count' => intval($package['preserve_count'] ?? 0),
                    'automatic_site_mutation' => false,
                );
            } elseif ($action_key === 'prepare_indexability_review') {
                $prepare_result = array(
                    'result_type' => 'canonical_execution_package_prepared',
                    'issue_family' => 'canonical',
                    'execution_package' => $package,
                    'canonical_execution_package' => $package,
                    'total_candidates' => intval($package['total_candidates'] ?? 0),
                    'self_canonical_count' => intval($package['self_canonical_count'] ?? 0),
                    'rewrite_count' => intval($package['rewrite_count'] ?? 0),
                    'manual_review_needed_count' => intval($package['manual_review_needed_count'] ?? 0),
                    'keep_count' => intval($package['keep_count'] ?? 0),
                    'automatic_site_mutation' => false,
                );
            }
            $changes = array(
                'approval_state' => 'prepared',
                'execution_state' => 'prepared',
                'execution_package_id' => $package_id,
                'last_prepared_at' => $now,
                'last_prepared_summary' => sprintf(
                    'Prepared %s work package for %d affected rows from Site Audit version %d.',
                    sanitize_text_field((string) ($bundle['bundle_type'] ?? $action_key)),
                    $affected_count,
                    $version_number
                ),
                'last_prepare_result' => $prepare_result,
                'metadata_execution_package' => $action_key === 'prepare_metadata_remediation' ? $package : array(),
                'canonical_execution_package' => $action_key === 'prepare_indexability_review' ? $package : array(),
                'image_alt_execution_package' => $action_key === 'prepare_image_alt_text' ? $package : array(),
                'last_error' => '',
            );
            $this->aysa_adjustments_update_site_audit_action_bundle_state($project_id, $action_key, $changes);
            return array(
                'applied' => true,
                'stage' => 'prepare',
                'execution_state' => 'prepared',
                'action_key' => $action_key,
                'execution_package_id' => $package_id,
                'affected_count' => $affected_count,
                'prepare_result' => $changes['last_prepare_result'],
                'metadata_execution_package' => $action_key === 'prepare_metadata_remediation' ? $package : array(),
                'canonical_execution_package' => $action_key === 'prepare_indexability_review' ? $package : array(),
                'image_alt_execution_package' => $action_key === 'prepare_image_alt_text' ? $package : array(),
                'version_number' => $version_number,
                'requires_approval' => true,
            );
        }

        if ($stage === 'ignore') {
            $changes = array(
                'approval_state' => 'dismissed',
                'execution_state' => 'ignored',
                'last_error' => '',
            );
            $this->aysa_adjustments_update_site_audit_action_bundle_state($project_id, $action_key, $changes);
            return array(
                'applied' => true,
                'stage' => 'ignore',
                'execution_state' => 'ignored',
                'action_key' => $action_key,
                'execution_package_id' => $package_id,
                'affected_count' => $affected_count,
            );
        }

        if ($stage === 'apply') {
            if (!in_array($action_key, array('prepare_metadata_remediation', 'prepare_image_alt_text', 'prepare_indexability_review'), true)) {
                $missing_endpoint_map = array(
                    'prepare_response_code_review' => 'redirect_cleanup_worker_endpoint_missing',
                    'prepare_indexability_review' => 'indexability_canonical_worker_endpoint_missing',
                    'prepare_internal_linking_plan' => 'site_audit_internal_linking_worker_endpoint_missing',
                );
                $reason = sanitize_key((string) ($missing_endpoint_map[$action_key] ?? 'shared_worker_endpoint_missing'));
                $result = array(
                    'result_type' => 'missing_shared_executor',
                    'execution_boundary' => sanitize_text_field((string) ($bundle['target_scope'] ?? 'review')),
                    'automatic_site_mutation' => false,
                    'message' => 'This Site Audit action family does not yet have a shared worker-backed executor. Agent8 did not apply a local fallback.',
                    'missing_endpoint' => $reason,
                );
                $this->aysa_agent8_create_site_audit_communication_event(
                    $project_id,
                    'site_audit_action_blocked',
                    'Agent8 Site Audit: ' . $this->aysa_technical_site_audit_action_subject_label($action_key) . ' blocked',
                    sprintf(
                        "I did not apply this Site Audit package because there is no shared worker-backed executor for it yet.\n\nAction: %s\nReason: %s\n\nNo local fallback was used. The next step is to add or wire the correct shared endpoint before this can safely mutate the site.",
                        $action_key,
                        $reason
                    ),
                    array(
                        'action_key' => $action_key,
                        'package_id' => $package_id,
                        'missing_endpoint' => $reason,
                    ),
                    'warning'
                );
                $this->aysa_adjustments_update_site_audit_action_bundle_state($project_id, $action_key, array(
                    'approval_state' => 'failed',
                    'execution_state' => 'failed',
                    'execution_package_id' => $package_id,
                    'last_applied_at' => $now,
                    'last_apply_result' => $result,
                    'last_error' => $reason,
                ));
                return array(
                    'applied' => true,
                    'blocked' => true,
                    'stage' => 'apply',
                    'execution_state' => 'failed',
                    'action_key' => $action_key,
                    'execution_package_id' => $package_id,
                    'affected_count' => $affected_count,
                    'apply_result' => $result,
                );
            }
            $result = array(
                'result_type' => 'execution_package_queued',
                'execution_boundary' => sanitize_text_field((string) ($bundle['target_scope'] ?? 'review')),
                'automatic_site_mutation' => false,
                'message' => 'The action package was moved into execution state. Site mutations still require the mapped worker/manual executor for this issue family.',
                'worker_action_keys' => array_values(array_unique(array_filter(array_map(static function($issue) {
                    return sanitize_key((string) ($issue['worker_action_key'] ?? ''));
                }, isset($bundle['issue_breakdown']) && is_array($bundle['issue_breakdown']) ? $bundle['issue_breakdown'] : array())))),
            );
            $changes = array(
                'approval_state' => 'applied',
                'execution_state' => 'applied',
                'execution_package_id' => $package_id,
                'last_applied_at' => $now,
                'last_apply_result' => $result,
                'last_error' => '',
            );
            $this->aysa_adjustments_update_site_audit_action_bundle_state($project_id, $action_key, $changes);
            return array(
                'applied' => true,
                'stage' => 'apply',
                'execution_state' => 'applied',
                'action_key' => $action_key,
                'execution_package_id' => $package_id,
                'affected_count' => $affected_count,
                'apply_result' => $result,
            );
        }

        $baseline_count = intval($payload['baseline_affected_count'] ?? ($bundle['affected_count'] ?? 0));
        if ($action_key === 'prepare_metadata_remediation') {
            return $this->aysa_technical_site_audit_verify_metadata_remediation($project_id, $bundle, $payload);
        }
        if ($action_key === 'prepare_response_code_review') {
            return $this->aysa_technical_site_audit_verify_response_code_review($project_id, $bundle, $payload);
        }
        if ($action_key === 'prepare_internal_linking_plan') {
            return $this->aysa_technical_site_audit_verify_internal_linking_plan($project_id, $bundle, $payload);
        }
        if ($action_key === 'prepare_image_alt_text') {
            return $this->aysa_technical_site_audit_verify_image_alt_text($project_id, $bundle, $payload);
        }
        if ($action_key === 'prepare_indexability_review') {
            return $this->aysa_technical_site_audit_verify_indexability_review($project_id, $bundle, $payload);
        }

        $current_count = $affected_count;
        $verified = $history_count > 1 && $current_count < $baseline_count;
        $verification_state = $verified ? 'verified' : 'verification_pending';
        $verify_result = array(
            'verification_method' => 'site_audit_delta',
            'baseline_affected_count' => $baseline_count,
            'current_affected_count' => $current_count,
            'history_count' => $history_count,
            'version_number' => $version_number,
            'verified' => $verified,
            'evidence' => $verified
                ? 'Latest Site Audit snapshot shows fewer affected rows than the baseline.'
                : 'No newer improving audit evidence exists yet; not marking this action verified.',
        );
        $changes = array(
            'execution_state' => $verification_state,
            'approval_state' => $verified ? 'verified' : 'verification_pending',
            'last_verified_at' => $now,
            'last_verify_result' => $verify_result,
            'last_error' => '',
        );
        $this->aysa_adjustments_update_site_audit_action_bundle_state($project_id, $action_key, $changes);
        return array(
            'applied' => true,
            'stage' => 'verify',
            'execution_state' => $verification_state,
            'action_key' => $action_key,
            'execution_package_id' => $package_id,
            'verification' => $verify_result,
        );
    }

    private function aysa_technical_site_audit_verify_internal_linking_plan($project_id, $bundle, $payload = array()) {
        $project_id = intval($project_id);
        $bundle = is_array($bundle) ? $bundle : array();
        $payload = is_array($payload) ? $payload : array();
        $now = current_time('mysql');
        $package_id = sanitize_text_field((string) ($bundle['execution_package_id'] ?? ''));
        $apply_result = isset($bundle['last_apply_result']['apply_result']) && is_array($bundle['last_apply_result']['apply_result'])
            ? $bundle['last_apply_result']['apply_result']
            : (isset($bundle['last_apply_result']) && is_array($bundle['last_apply_result']) ? $bundle['last_apply_result'] : array());
        $checked = 0;
        $verified = 0;
        $results = array();
        foreach ((array) ($apply_result['results'] ?? array()) as $row) {
            if (!is_array($row) || sanitize_key((string) ($row['status'] ?? '')) !== 'applied') {
                continue;
            }
            $source_post_id = intval($row['source_post_id'] ?? 0);
            $target_url = esc_url_raw((string) ($row['target_url'] ?? ''));
            if ($source_post_id <= 0 || $target_url === '') {
                continue;
            }
            $checked++;
            $post = get_post($source_post_id);
            $content = $post && isset($post->post_content) ? (string) $post->post_content : '';
            $found = $content !== '' && strpos($content, $target_url) !== false;
            if ($found) {
                $verified++;
            }
            $results[] = array(
                'source_post_id' => $source_post_id,
                'target_url' => $target_url,
                'verified' => $found,
                'reason' => $found ? 'target_link_found_in_source_content' : 'target_link_not_found_in_source_content',
            );
        }
        $is_verified = $checked > 0 && $checked === $verified;
        $verify_result = $this->aysa_technical_site_audit_normalize_verification_result(array(
            'verification_method' => 'live_internal_link_readback',
            'checked_count' => $checked,
            'verified_count' => $verified,
            'verified' => $is_verified,
            'evidence' => $is_verified
                ? 'Every applied scoped internal link is present in the source post content.'
                : 'Not every applied scoped internal link can be confirmed by live source-content readback.',
            'results' => $results,
        ));
        $this->aysa_adjustments_update_site_audit_action_bundle_state($project_id, 'prepare_internal_linking_plan', array(
            'execution_state' => $is_verified ? 'verified' : 'verification_pending',
            'approval_state' => $is_verified ? 'verified' : 'verification_pending',
            'last_verified_at' => $now,
            'last_verify_result' => $verify_result,
            'last_error' => '',
        ));
        return array(
            'applied' => true,
            'stage' => 'verify',
            'execution_state' => $is_verified ? 'verified' : 'verification_pending',
            'action_key' => 'prepare_internal_linking_plan',
            'execution_package_id' => $package_id,
            'verification' => $verify_result,
        );
    }

    private function aysa_technical_site_audit_verify_image_alt_text($project_id, $bundle, $payload = array()) {
        $project_id = intval($project_id);
        $bundle = is_array($bundle) ? $bundle : array();
        $now = current_time('mysql');
        $package_id = sanitize_text_field((string) ($bundle['execution_package_id'] ?? ''));
        $apply_result = isset($bundle['last_apply_result']['apply_result']) && is_array($bundle['last_apply_result']['apply_result'])
            ? $bundle['last_apply_result']['apply_result']
            : (isset($bundle['last_apply_result']) && is_array($bundle['last_apply_result']) ? $bundle['last_apply_result'] : array());
        $checked = 0;
        $verified = 0;
        $results = array();
        foreach ((array) ($apply_result['results'] ?? array()) as $row) {
            if (!is_array($row) || !in_array(sanitize_key((string) ($row['status'] ?? '')), array('success', 'already_present'), true)) {
                continue;
            }
            $attachment_id = intval($row['attachment_id'] ?? 0);
            $expected = sanitize_text_field((string) ($row['proposed_value'] ?? ''));
            if ($attachment_id <= 0 || $expected === '') {
                continue;
            }
            $checked++;
            $current = sanitize_text_field((string) get_post_meta($attachment_id, '_wp_attachment_image_alt', true));
            $matches = $current === $expected;
            if ($matches) {
                $verified++;
            }
            $results[] = array(
                'attachment_id' => $attachment_id,
                'image_url' => esc_url_raw((string) ($row['image_url'] ?? '')),
                'expected_value' => $expected,
                'current_value' => $current,
                'verified' => $matches,
            );
        }
        $is_verified = $checked > 0 && $checked === $verified;
        $verify_result = $this->aysa_technical_site_audit_normalize_verification_result(array(
            'verification_method' => 'live_attachment_alt_readback',
            'checked_count' => $checked,
            'verified_count' => $verified,
            'verified' => $is_verified,
            'evidence' => $is_verified
                ? 'Every applied image alt value matches the live WordPress attachment alt readback.'
                : 'Not every applied image alt value matches the live WordPress attachment alt readback yet.',
            'results' => $results,
        ));
        $verify_result['communication_event'] = $this->aysa_agent8_create_site_audit_communication_event(
            $project_id,
            $is_verified ? 'site_audit_image_alt_verify_completed' : 'site_audit_image_alt_verify_pending',
            $is_verified ? 'Agent8 Site Audit: Image alt text verified' : 'Agent8 Site Audit: Image alt text verification pending',
            $is_verified
                ? sprintf("I checked the live attachment alt text and verified all %d applied image alt changes.", $checked)
                : sprintf("I checked the live attachment alt text, but only %d of %d applied image alt changes are confirmed.", $verified, $checked),
            array(
                'action_key' => 'prepare_image_alt_text',
                'checked_count' => $checked,
                'verified_count' => $verified,
                'verification_method' => 'live_attachment_alt_readback',
            ),
            $is_verified ? 'info' : 'warning'
        );
        $this->aysa_adjustments_update_site_audit_action_bundle_state($project_id, 'prepare_image_alt_text', array(
            'execution_state' => $is_verified ? 'verified' : 'verification_pending',
            'approval_state' => $is_verified ? 'verified' : 'verification_pending',
            'last_verified_at' => $now,
            'last_verify_result' => $verify_result,
            'last_error' => '',
        ));
        return array(
            'applied' => true,
            'stage' => 'verify',
            'execution_state' => $is_verified ? 'verified' : 'verification_pending',
            'action_key' => 'prepare_image_alt_text',
            'execution_package_id' => $package_id,
            'verification' => $verify_result,
        );
    }

    private function aysa_technical_site_audit_verify_indexability_review($project_id, $bundle, $payload = array()) {
        $project_id = intval($project_id);
        $bundle = is_array($bundle) ? $bundle : array();
        $now = current_time('mysql');
        $package_id = sanitize_text_field((string) ($bundle['execution_package_id'] ?? ''));
        $apply_result = isset($bundle['last_apply_result']['apply_result']) && is_array($bundle['last_apply_result']['apply_result'])
            ? $bundle['last_apply_result']['apply_result']
            : (isset($bundle['last_apply_result']) && is_array($bundle['last_apply_result']) ? $bundle['last_apply_result'] : array());
        $checked = 0;
        $verified = 0;
        $results = array();
        foreach ((array) ($apply_result['results'] ?? array()) as $row) {
            if (!is_array($row) || sanitize_key((string) ($row['status'] ?? '')) !== 'success') {
                continue;
            }
            $page_url = esc_url_raw((string) ($row['page_url'] ?? ''));
            $expected = esc_url_raw((string) ($row['expected_canonical'] ?? ''));
            if ($page_url === '' || $expected === '') {
                continue;
            }
            $checked++;
            $response = wp_remote_get($page_url, array('timeout' => 20, 'redirection' => 3, 'user-agent' => 'AYSA Site Audit Canonical Verify'));
            $body = is_wp_error($response) ? '' : (string) wp_remote_retrieve_body($response);
            $metrics = $this->aysa_local_site_audit_extract_html_metrics($body, $page_url);
            $current = esc_url_raw((string) ($metrics['canonical'] ?? ''));
            $matches = untrailingslashit($current) === untrailingslashit($expected);
            if ($matches) {
                $verified++;
            }
            $results[] = array(
                'page_url' => $page_url,
                'expected_canonical' => $expected,
                'current_canonical' => $current,
                'verified' => $matches,
            );
        }
        $is_verified = $checked > 0 && $checked === $verified;
        $verify_result = $this->aysa_technical_site_audit_normalize_verification_result(array(
            'verification_method' => 'live_html_canonical_readback',
            'checked_count' => $checked,
            'verified_count' => $verified,
            'verified' => $is_verified,
            'evidence' => $is_verified
                ? 'Every applied canonical matches the live HTML canonical readback.'
                : 'Not every applied canonical matches the live HTML canonical readback yet.',
            'results' => $results,
        ));
        $verify_result['communication_event'] = $this->aysa_agent8_create_site_audit_communication_event(
            $project_id,
            $is_verified ? 'site_audit_canonical_verify_completed' : 'site_audit_canonical_verify_pending',
            $is_verified ? 'Agent8 Site Audit: Canonical verified' : 'Agent8 Site Audit: Canonical verification pending',
            $is_verified
                ? sprintf("I checked the live HTML and confirmed all %d canonical changes via canonical readback.", $checked)
                : sprintf("I checked the live HTML, but only %d of %d canonical changes are confirmed so far.", $verified, $checked),
            array(
                'action_key' => 'prepare_indexability_review',
                'checked_count' => $checked,
                'verified_count' => $verified,
                'verification_method' => 'live_html_canonical_readback',
            ),
            $is_verified ? 'info' : 'warning'
        );
        $this->aysa_adjustments_update_site_audit_action_bundle_state($project_id, 'prepare_indexability_review', array(
            'execution_state' => $is_verified ? 'verified' : 'verification_pending',
            'approval_state' => $is_verified ? 'verified' : 'verification_pending',
            'last_verified_at' => $now,
            'last_verify_result' => $verify_result,
            'last_error' => '',
        ));
        return array(
            'applied' => true,
            'stage' => 'verify',
            'execution_state' => $is_verified ? 'verified' : 'verification_pending',
            'action_key' => 'prepare_indexability_review',
            'execution_package_id' => $package_id,
            'verification' => $verify_result,
        );
    }

    private function aysa_adjustments_build_site_audit_bundle_approval_request($action_key, $action_payload = array()) {
        $action_key = sanitize_key((string) $action_key);
        $action_payload = is_array($action_payload) ? $action_payload : array();
        if ($action_key === '' || !$this->aysa_adjustments_is_site_audit_bundle_action_key($action_key)) {
            return null;
        }

        $label = sanitize_text_field((string) ($action_payload['action_label'] ?? 'Prepare technical remediation bundle'));
        $issue_title = sanitize_text_field((string) ($action_payload['issue_title'] ?? 'Technical remediation bundle'));
        $affected_count = max(0, intval($action_payload['affected_count'] ?? 0));
        $source_files = isset($action_payload['source_files']) && is_array($action_payload['source_files'])
            ? array_values(array_filter(array_map('sanitize_text_field', $action_payload['source_files'])))
            : array();
        $next_step = sanitize_text_field((string) ($action_payload['next_step'] ?? 'Review the remediation preparation output in Agent8.'));

        return array(
            'action_key' => $action_key,
            'title' => $label !== '' ? $label : 'Confirm technical remediation bundle',
            'message' => 'Agent8 will prepare the remediation workspace for this audit finding, without changing the site directly.',
            'confirmation_label' => 'Approve bundle',
            'cancel_label' => 'Not now',
            'summary' => array(
                'issue' => $issue_title,
                'affected_urls' => $affected_count,
                'source_files' => $source_files,
                'next_step' => $next_step,
            ),
        );
    }

    private function aysa_adjustments_get_technical_pagespeed_snapshot($project_id) {
        $project_id = intval($project_id);
        $state = get_option($this->aysa_technical_pagespeed_state_key($project_id), array());
        if (!is_array($state)) {
            $state = array();
        }
        $state_status = sanitize_key((string) ($state['status'] ?? 'idle'));

        $data = !empty($state['data']) && is_array($state['data']) ? $state['data'] : array();
        $overview = !empty($data['overview']) && is_array($data['overview']) ? $data['overview'] : array();
        $desktop = !empty($data['desktop']) && is_array($data['desktop']) ? $data['desktop'] : array();
        $mobile = !empty($data['mobile']) && is_array($data['mobile']) ? $data['mobile'] : array();
        $conclusion = !empty($data['conclusion']) && is_array($data['conclusion']) ? $data['conclusion'] : array();

        $desktop_metrics = !empty($desktop['metrics']) && is_array($desktop['metrics']) ? $desktop['metrics'] : array();
        $mobile_metrics = !empty($mobile['metrics']) && is_array($mobile['metrics']) ? $mobile['metrics'] : array();
        $desktop_opportunities = !empty($desktop['opportunities']) && is_array($desktop['opportunities']) ? array_values($desktop['opportunities']) : array();
        $mobile_opportunities = !empty($mobile['opportunities']) && is_array($mobile['opportunities']) ? array_values($mobile['opportunities']) : array();
        $priority_actions = !empty($conclusion['priority_actions']) && is_array($conclusion['priority_actions']) ? array_values($conclusion['priority_actions']) : array();

        $overview_rows = array(
            array('metric' => 'Overall score', 'value' => (string) intval($overview['overallScore'] ?? $conclusion['overall_score'] ?? 0)),
            array('metric' => 'Desktop score', 'value' => (string) intval($overview['pageSpeedDesktop'] ?? $desktop_metrics['performance']['score_percent'] ?? 0)),
            array('metric' => 'Mobile score', 'value' => (string) intval($overview['pageSpeedMobile'] ?? $mobile_metrics['performance']['score_percent'] ?? 0)),
            array('metric' => 'Mobile friendly', 'value' => (string) intval($overview['mobileFriendly'] ?? 0)),
            array('metric' => 'Indexability', 'value' => (string) intval($overview['indexability'] ?? 0)),
            array('metric' => 'Status', 'value' => sanitize_text_field((string) ($conclusion['status'] ?? ''))),
        );

        $metrics_rows = array();
        $metric_map = array(
            'firstContentfulPaint' => 'First Contentful Paint',
            'largestContentfulPaint' => 'Largest Contentful Paint',
            'totalBlockingTime' => 'Total Blocking Time',
            'cumulativeLayoutShift' => 'Cumulative Layout Shift',
            'speedIndex' => 'Speed Index',
            'interactive' => 'Time to Interactive',
            'totalByteWeight' => 'Total Byte Weight',
        );
        foreach ($metric_map as $key => $label) {
            $metrics_rows[] = array(
                'metric' => $label,
                'desktop' => (string) ($desktop_metrics[$key] ?? ''),
                'mobile' => (string) ($mobile_metrics[$key] ?? ''),
            );
        }

        $opportunities_rows = array();
        foreach (array('desktop' => $desktop_opportunities, 'mobile' => $mobile_opportunities) as $strategy => $rows) {
            foreach ($rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $opportunities_rows[] = array(
                    'id' => sanitize_key((string) ($row['id'] ?? '')),
                    'strategy' => $strategy,
                    'title' => sanitize_text_field((string) ($row['title'] ?? '')),
                    'description' => sanitize_text_field((string) ($row['description'] ?? '')),
                    'display_value' => sanitize_text_field((string) ($row['displayValue'] ?? '')),
                    'score' => isset($row['score']) ? floatval($row['score']) : null,
                    'impact' => intval($row['impact'] ?? 0),
                    'savings_ms' => intval(round(floatval($row['savings_ms'] ?? 0))),
                    'savings_bytes' => intval(round(floatval($row['savings_bytes'] ?? 0))),
                );
            }
        }

        $priority_rows = array();
        foreach ($priority_actions as $action) {
            if (is_string($action) && trim($action) !== '') {
                $priority_rows[] = array(
                    'title' => sanitize_text_field($action),
                );
                continue;
            }
            if (!is_array($action)) {
                continue;
            }
            $priority_rows[] = array(
                'id' => sanitize_key((string) ($action['id'] ?? '')),
                'title' => sanitize_text_field((string) ($action['title'] ?? $action['action'] ?? $action['name'] ?? '')),
                'displayValue' => sanitize_text_field((string) ($action['displayValue'] ?? $action['display_value'] ?? $action['details'] ?? '')),
                'severity' => sanitize_key((string) ($action['severity'] ?? $action['status'] ?? 'notice')),
            );
        }

        $has_data = !empty($data) || !empty($opportunities_rows) || intval($overview['overallScore'] ?? 0) > 0;
        $snapshot_status = $has_data ? 'ready' : (in_array($state_status, array('queued', 'running', 'completed', 'failed', 'blocked'), true) ? $state_status : 'idle');

        return array(
            'id' => 'technical_pagespeed',
            'status' => $snapshot_status,
            'updated_at' => sanitize_text_field((string) ($state['updated_at'] ?? '')),
            'url' => esc_url_raw((string) ($data['url'] ?? '')),
            'overall_score' => intval($overview['overallScore'] ?? $conclusion['overall_score'] ?? 0),
            'desktop_score' => intval($overview['pageSpeedDesktop'] ?? $desktop_metrics['performance']['score_percent'] ?? 0),
            'mobile_score' => intval($overview['pageSpeedMobile'] ?? $mobile_metrics['performance']['score_percent'] ?? 0),
            'mobile_friendly' => intval($overview['mobileFriendly'] ?? 0),
            'indexability' => intval($overview['indexability'] ?? 0),
            'summary' => sanitize_text_field((string) ($conclusion['summary'] ?? '')),
            'priority_actions' => $priority_rows,
            'overview_rows' => array_values($overview_rows),
            'rows' => array_values($opportunities_rows),
            'metrics_rows' => array_values($metrics_rows),
            'count' => count($opportunities_rows),
            'has_data' => $has_data,
            'raw_payload_available' => !empty($data['raw']) && is_array($data['raw']),
            'run_id' => intval($state['run_id'] ?? 0),
            'last_report_email' => isset($state['last_report_email']) && is_array($state['last_report_email']) ? $state['last_report_email'] : array(),
        );
    }


    private function aysa_adjustments_get_technical_mobile_optimization_snapshot($project_id) {
        $pagespeed = $this->aysa_adjustments_get_technical_pagespeed_snapshot($project_id);
        $rows = isset($pagespeed['rows']) && is_array($pagespeed['rows']) ? $pagespeed['rows'] : array();
        $mobile_rows = array_values(array_filter($rows, static function ($row) {
            return is_array($row) && strtolower(trim((string) ($row['strategy'] ?? ''))) === 'mobile';
        }));

        return array(
            'id' => 'technical_mobile_optimization',
            'status' => !empty($pagespeed['has_data']) ? 'ready' : 'idle',
            'updated_at' => sanitize_text_field((string) ($pagespeed['updated_at'] ?? '')),
            'url' => esc_url_raw((string) ($pagespeed['url'] ?? '')),
            'mobile_score' => intval($pagespeed['mobile_score'] ?? 0),
            'mobile_friendly' => intval($pagespeed['mobile_friendly'] ?? 0),
            'indexability' => intval($pagespeed['indexability'] ?? 0),
            'summary' => sanitize_text_field((string) ($pagespeed['summary'] ?? '')),
            'priority_actions' => isset($pagespeed['priority_actions']) && is_array($pagespeed['priority_actions']) ? $pagespeed['priority_actions'] : array(),
            'overview_rows' => array(
                array('metric' => 'Mobile score', 'value' => (string) intval($pagespeed['mobile_score'] ?? 0)),
                array('metric' => 'Mobile friendly', 'value' => (string) intval($pagespeed['mobile_friendly'] ?? 0)),
                array('metric' => 'Indexability', 'value' => (string) intval($pagespeed['indexability'] ?? 0)),
            ),
            'rows' => $mobile_rows,
            'metrics_rows' => isset($pagespeed['metrics_rows']) && is_array($pagespeed['metrics_rows']) ? array_values($pagespeed['metrics_rows']) : array(),
            'count' => count($mobile_rows),
            'has_data' => !empty($pagespeed['has_data']),
        );
    }

    private function aysa_adjustments_get_technical_404_errors_snapshot($project_id) {
        global $wpdb;

        $project_id = intval($project_id);
        $state_key = 'aysa_technical_site_audit_state_' . $project_id;
        $state = get_option($state_key, array());
        $snapshot = (is_array($state) && !empty($state['audit_snapshot']) && is_array($state['audit_snapshot']))
            ? $state['audit_snapshot']
            : array();

        $rows = array();
        $seen = array();
        $catalog_items = $this->aysa_adjustments_get_wp_catalog_items();

        if (!empty($snapshot['pages']) && is_array($snapshot['pages'])) {
            foreach ($snapshot['pages'] as $page) {
                if (!is_array($page)) {
                    continue;
                }
                if (intval($page['status_code'] ?? 0) !== 404) {
                    continue;
                }
                $url = trim((string) ($page['url'] ?? ''));
                if ($url === '' || isset($seen[$url])) {
                    continue;
                }
                $seen[$url] = true;
                $candidate = $this->aysa_adjustments_guess_redirect_target_for_url($url, $catalog_items);
                $rows[] = array(
                    'id' => 0,
                    'url' => $url,
                    'status_code' => 404,
                    'source' => 'site_audit',
                    'hits' => 0,
                    'best_redirect_url' => $candidate !== null ? (string) ($candidate['url'] ?? '') : '',
                    'best_redirect_confidence' => $candidate !== null ? floatval($candidate['score'] ?? 0) : 0.0,
                    'best_redirect_action' => $candidate !== null ? 'redirect' : 'none',
                    'last_hit' => sanitize_text_field((string) ($state['updated_at'] ?? '')),
                );
            }
        }

        $table_name = esc_sql($wpdb->prefix . 'aysa_404_errors');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $local_rows = $wpdb->get_results(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT id, url, hit_count, last_hit, suggested_url FROM {$table_name} WHERE is_ignored = 0 ORDER BY last_hit DESC LIMIT 200",
            ARRAY_A
        );
        if (is_array($local_rows)) {
            foreach ($local_rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $url = trim((string) ($row['url'] ?? ''));
                if ($url === '' || isset($seen[$url])) {
                    continue;
                }
                $seen[$url] = true;
                $suggested_url = (string) ($row['suggested_url'] ?? '');
                $suggested_confidence = trim($suggested_url) !== '' ? 1.0 : 0.0;
                if (trim($suggested_url) === '') {
                    $candidate = $this->aysa_adjustments_guess_redirect_target_for_url($url, $catalog_items);
                    if ($candidate !== null) {
                        $suggested_url = (string) ($candidate['url'] ?? '');
                        $suggested_confidence = floatval($candidate['score'] ?? 0);
                    }
                }
                $rows[] = array(
                    'id' => intval($row['id'] ?? 0),
                    'url' => $url,
                    'status_code' => 404,
                    'source' => 'tracker',
                    'hits' => intval($row['hit_count'] ?? 0),
                    'best_redirect_url' => $suggested_url,
                    'best_redirect_confidence' => $suggested_confidence,
                    'best_redirect_action' => trim($suggested_url) !== '' ? 'redirect' : 'none',
                    'last_hit' => sanitize_text_field((string) ($row['last_hit'] ?? '')),
                );
            }
        }

        usort($rows, static function ($a, $b) {
            return strcmp((string) ($b['last_hit'] ?? ''), (string) ($a['last_hit'] ?? ''));
        });

        $audit_count = 0;
        $tracker_count = 0;
        foreach ($rows as $row) {
            if (($row['source'] ?? '') === 'site_audit') {
                $audit_count++;
            } elseif (($row['source'] ?? '') === 'tracker') {
                $tracker_count++;
            }
        }

        return array(
            'id' => 'technical_404_errors',
            'status' => !empty($rows) ? 'ready' : 'idle',
            'updated_at' => sanitize_text_field((string) ($state['updated_at'] ?? '')),
            'count' => count($rows),
            'audit_count' => $audit_count,
            'tracker_count' => $tracker_count,
            'overview_rows' => array(
                array('metric' => '404 URLs', 'value' => (string) count($rows)),
                array('metric' => 'From site audit', 'value' => (string) $audit_count),
                array('metric' => 'From local tracker', 'value' => (string) $tracker_count),
            ),
            'rows' => array_values($rows),
            'has_data' => !empty($rows),
        );
    }

    private function aysa_adjustments_guess_redirect_target_for_url($url, $catalog_items) {
        $path = (string) wp_parse_url((string) $url, PHP_URL_PATH);
        $query = (string) wp_parse_url((string) $url, PHP_URL_QUERY);
        $source = trim($path !== '' ? $path : (string) $url);
        if ($query !== '' && $path !== '') {
            $source .= ' ' . str_replace(array('=', '&', '%'), ' ', $query);
        }

        $source = trim($source, '/');
        if ($source === '') {
            return null;
        }

        $tokens = $this->aysa_adjustments_tokenize_keyword_text(str_replace(array('-', '_', '/'), ' ', $source));
        if (empty($tokens)) {
            return null;
        }

        $weak_tokens = $this->aysa_adjustments_get_weak_context_tokens();
        $tokens = array_values(array_diff($tokens, array_merge($weak_tokens, array(
            'wp', 'admin', 'api', 'uploads', 'upload', 'page', 'id', 'php', 'href', 'project', 'domain',
            'about', 'our', 'ours', 'us', 'www', 'http', 'https', 'com', 'ro',
            'product', 'products', 'category', 'categories', 'tag', 'tags', 'attachment', 'media',
            'feed', 'index', 'html', 'htm', 'amp', 'page_id', 'utm', 'source'
        ))));
        if (empty($tokens)) {
            return null;
        }

        if (in_array('contact', $tokens, true)) {
            foreach ((array) $catalog_items as $item) {
                $candidate_url = (string) ($item['url'] ?? '');
                $candidate_path = (string) wp_parse_url($candidate_url, PHP_URL_PATH);
                if ($candidate_path !== '' && str_contains($candidate_path, '/contact')) {
                    return array(
                        'type' => (string) ($item['type'] ?? ''),
                        'id' => intval($item['id'] ?? 0),
                        'name' => (string) ($item['name'] ?? ''),
                        'url' => $candidate_url,
                        'score' => 0.95,
                    );
                }
            }
        }

        if (in_array('shop', $tokens, true)) {
            return array(
                'type' => 'Page',
                'id' => 0,
                'name' => 'Shop',
                'url' => home_url('/shop/'),
                'score' => 0.9,
            );
        }

        if (count($tokens) < 2) {
            return null;
        }

        $best = null;
        $best_overlap = 0;
        foreach ((array) $catalog_items as $item) {
            if (!is_array($item) || empty($item['tokens']) || empty($item['url'])) {
                continue;
            }
            $item_tokens = is_array($item['tokens']) ? array_values($item['tokens']) : array();
            $overlap = count(array_intersect($tokens, $item_tokens));
            if ($overlap < 2) {
                continue;
            }

            $score = ($overlap / max(count($tokens), 1)) * 0.72
                + ($overlap / max(count($item_tokens), 1)) * 0.28;
            $item_text = strtolower(trim(remove_accents((string) ($item['text'] ?? ''))));
            $token_phrase = implode(' ', $tokens);
            if ($item_text !== '' && str_contains($item_text, $token_phrase)) {
                $score += 0.18;
            }

            if ($best === null || $score > floatval($best['score'] ?? 0)) {
                $best = array(
                    'type' => (string) ($item['type'] ?? ''),
                    'id' => intval($item['id'] ?? 0),
                    'name' => (string) ($item['name'] ?? ''),
                    'url' => (string) ($item['url'] ?? ''),
                    'score' => min(1, $score),
                );
                $best_overlap = $overlap;
            }
        }

        if ($best === null || floatval($best['score'] ?? 0) < 0.58 || $best_overlap < 2) {
            return null;
        }

        $best_path = (string) wp_parse_url((string) ($best['url'] ?? ''), PHP_URL_PATH);
        if ($best_path !== '' && strcasecmp(rtrim($best_path, '/'), '/' . trim($source, '/')) === 0) {
            return null;
        }

        return $best;
    }

    private function aysa_adjustments_get_technical_301_redirects_snapshot($project_id) {
        $htaccess_file = ABSPATH . '.htaccess';
        if (!file_exists($htaccess_file) || !is_readable($htaccess_file)) {
            return array(
                'id' => 'technical_301_redirects',
                'count' => 0,
                'rows' => array(),
                'has_data' => false,
            );
        }

        $content = file_get_contents($htaccess_file);
        if (!is_string($content) || trim($content) === '') {
            return array(
                'id' => 'technical_301_redirects',
                'count' => 0,
                'rows' => array(),
                'has_data' => false,
            );
        }

        $section_start = '# BEGIN Aysa Redirects';
        $section_end = '# END Aysa Redirects';
        $rows = array();

        if (preg_match('/' . preg_quote($section_start, '/') . '(.*?)' . preg_quote($section_end, '/') . '/s', $content, $matches) === 1) {
            $section = trim((string) ($matches[1] ?? ''));
            $lines = preg_split('/\R+/', $section);
            if (is_array($lines)) {
                foreach ($lines as $line) {
                    $line = trim((string) $line);
                    if ($line === '' || str_starts_with($line, '#')) {
                        continue;
                    }

                    if (preg_match('/^RedirectMatch\s+301\s+\^(.+?)\$\s+(.+)$/i', $line, $rule) !== 1) {
                        continue;
                    }

                    $source = trim((string) ($rule[1] ?? ''));
                    $target = trim((string) ($rule[2] ?? ''));
                    if ($source === '' || $target === '') {
                        continue;
                    }

                    $decoded_source = stripcslashes($source);
                    if ($decoded_source === '' || $decoded_source[0] !== '/') {
                        $decoded_source = '/' . ltrim($decoded_source, '/');
                    }

                    if (!preg_match('#^https?://#i', $target)) {
                        $target = '/' . ltrim($target, '/');
                    }

                    $rows[] = array(
                        'source_url' => sanitize_text_field($decoded_source),
                        'target_url' => esc_url_raw($target),
                        'status' => '301',
                        'rule_type' => 'manual',
                        'source' => 'htaccess',
                    );
                }
            }
        }

        return array(
            'id' => 'technical_301_redirects',
            'count' => count($rows),
            'rows' => $rows,
            'has_data' => !empty($rows),
            'updated_at' => @filemtime($htaccess_file) ?: 0,
        );
    }

    private function aysa_adjustments_get_technical_410_gone_snapshot($project_id) {
        $htaccess_file = ABSPATH . '.htaccess';
        if (!file_exists($htaccess_file) || !is_readable($htaccess_file)) {
            return array(
                'id' => 'technical_410_gone',
                'count' => 0,
                'rows' => array(),
                'has_data' => false,
            );
        }

        $content = file_get_contents($htaccess_file);
        if (!is_string($content) || trim($content) === '') {
            return array(
                'id' => 'technical_410_gone',
                'count' => 0,
                'rows' => array(),
                'has_data' => false,
            );
        }

        $section_start = '# BEGIN Aysa Redirects';
        $section_end = '# END Aysa Redirects';
        $rows = array();

        if (preg_match('/' . preg_quote($section_start, '/') . '(.*?)' . preg_quote($section_end, '/') . '/s', $content, $matches) === 1) {
            $section = trim((string) ($matches[1] ?? ''));
            $lines = preg_split('/\R+/', $section);
            if (is_array($lines)) {
                foreach ($lines as $line) {
                    $line = trim((string) $line);
                    if ($line === '' || str_starts_with($line, '#')) {
                        continue;
                    }

                    if (preg_match('/^RedirectMatch\s+410\s+\^(.+?)\$$/i', $line, $rule) !== 1) {
                        continue;
                    }

                    $url = trim((string) ($rule[1] ?? ''));
                    if ($url === '') {
                        continue;
                    }

                    $decoded_url = stripcslashes($url);
                    if ($decoded_url === '' || $decoded_url[0] !== '/') {
                        $decoded_url = '/' . ltrim($decoded_url, '/');
                    }

                    $rows[] = array(
                        'url' => sanitize_text_field($decoded_url),
                        'status' => '410',
                        'rule_type' => 'manual',
                        'source' => 'htaccess',
                    );
                }
            }
        }

        return array(
            'id' => 'technical_410_gone',
            'count' => count($rows),
            'rows' => $rows,
            'has_data' => !empty($rows),
            'updated_at' => @filemtime($htaccess_file) ?: 0,
        );
    }

    private function aysa_adjustments_get_technical_errors_snapshot($project_id, array $technical_404_errors = array(), array $technical_301_redirects = array(), array $technical_410_gone = array()) {
        $project_id = intval($project_id);

        if (empty($technical_404_errors)) {
            $technical_404_errors = $this->aysa_adjustments_get_technical_404_errors_snapshot($project_id);
        }
        if (empty($technical_301_redirects)) {
            $technical_301_redirects = $this->aysa_adjustments_get_technical_301_redirects_snapshot($project_id);
        }
        if (empty($technical_410_gone)) {
            $technical_410_gone = $this->aysa_adjustments_get_technical_410_gone_snapshot($project_id);
        }

        $pending_reviews = get_option('aysa_pending_redirect_reviews', array());
        if (!is_array($pending_reviews)) {
            $pending_reviews = array();
        }

        $rows = array();
        $conflicts = array();
        $redirect_targets = array();
        $gone_urls = array();
        $pending_review_rows = array();

        foreach ((array) ($technical_301_redirects['rows'] ?? array()) as $row) {
            if (!is_array($row)) {
                continue;
            }

            $source_url = $this->aysa_normalize_redirect_path((string) ($row['source_url'] ?? ''));
            $target_url = trim((string) ($row['target_url'] ?? ''));
            if ($source_url === '') {
                continue;
            }

            $redirect_targets[$source_url] = array(
                'target_url' => $target_url,
                'source' => (string) ($row['source'] ?? 'htaccess'),
            );

            $rows[] = array(
                'kind' => '301',
                'source_url' => $source_url,
                'target_url' => $target_url,
                'status' => (string) ($row['status'] ?? '301'),
                'origin' => (string) ($row['source'] ?? 'htaccess'),
                'hits' => intval($row['hit_count'] ?? 0),
                'last_seen' => sanitize_text_field((string) ($row['created_at'] ?? '')),
                'seo_priority' => 'medium',
                'suggested_action' => 'redirect',
                'suggested_target_url' => $target_url,
                'suggested_confidence' => 100,
                'suggested_reason' => 'Existing redirect rule is active for this source URL.',
                'history_signals' => array('existing_301_rule'),
                'approval_state' => 'applied',
                'conflict_flags' => array(),
            );
        }

        foreach ((array) ($technical_410_gone['rows'] ?? array()) as $row) {
            if (!is_array($row)) {
                continue;
            }

            $source_url = $this->aysa_normalize_redirect_path((string) ($row['url'] ?? ''));
            if ($source_url === '') {
                continue;
            }
            $gone_urls[$source_url] = true;

            $rows[] = array(
                'kind' => '410',
                'source_url' => $source_url,
                'target_url' => '',
                'status' => (string) ($row['status'] ?? '410'),
                'origin' => (string) ($row['source'] ?? 'htaccess'),
                'hits' => 0,
                'last_seen' => sanitize_text_field((string) ($row['created_at'] ?? '')),
                'seo_priority' => 'medium',
                'suggested_action' => 'gone',
                'suggested_target_url' => '',
                'suggested_confidence' => 100,
                'suggested_reason' => 'URL is intentionally marked as gone.',
                'history_signals' => array('existing_410_rule'),
                'approval_state' => 'applied',
                'conflict_flags' => array(),
            );
        }

        foreach ($pending_reviews as $review_key => $review) {
            if (!is_array($review)) {
                continue;
            }
            $source_url = $this->aysa_normalize_redirect_path((string) ($review['source_url'] ?? ''));
            if ($source_url === '') {
                continue;
            }
            $pending_review_rows[] = array(
                'review_key' => sanitize_text_field((string) $review_key),
                'source_url' => $source_url,
                'confidence_score' => floatval($review['confidence_score'] ?? 0),
                'suggestions' => array_values(array_filter((array) ($review['suggestions'] ?? array()), 'is_array')),
                'created_at' => sanitize_text_field((string) ($review['created_at'] ?? '')),
            );
        }

        $noise_count = 0;
        $redirect_ready = 0;
        $gone_candidates = 0;
        $review_required = 0;

        foreach ((array) ($technical_404_errors['rows'] ?? array()) as $row) {
            if (!is_array($row)) {
                continue;
            }

            $source_url = $this->aysa_normalize_redirect_path((string) ($row['url'] ?? ''));
            if ($source_url === '') {
                continue;
            }

            $best_redirect_url = trim((string) ($row['best_redirect_url'] ?? ''));
            $best_redirect_confidence = floatval($row['best_redirect_confidence'] ?? 0);
            if ($best_redirect_confidence > 0 && $best_redirect_confidence <= 1) {
                $best_redirect_confidence = round($best_redirect_confidence * 100);
            } else {
                $best_redirect_confidence = round($best_redirect_confidence);
            }

            $is_noise = false;
            $lower_source = strtolower($source_url);
            $lower_referer = strtolower((string) ($row['referer'] ?? ''));
            $noise_needles = array('/wp-admin/', '/wp-login.php', '/xmlrpc.php', '/.env', '/admin', 'wp-json', 'rest_route=', 'author=', 'feed=', '?s=');
            foreach ($noise_needles as $needle) {
                if (strpos($lower_source, $needle) !== false || strpos($lower_referer, $needle) !== false) {
                    $is_noise = true;
                    break;
                }
            }

            $suggested_action = 'review';
            $suggested_reason = 'Manual review is recommended before applying a redirect or 410 rule.';
            $seo_priority = intval($row['hits'] ?? 0) > 0 ? 'high' : 'medium';
            $history_signals = array((string) ($row['source'] ?? 'tracker'));
            $approval_state = 'suggested';
            $conflict_flags = array();

            if ($is_noise) {
                $suggested_action = 'ignore';
                $suggested_reason = 'This 404 looks like admin, probe, or crawler noise and should not trigger an SEO redirect.';
                $seo_priority = 'low';
                $noise_count++;
                $history_signals[] = 'noise_pattern';
            } elseif (isset($gone_urls[$source_url])) {
                $suggested_action = 'gone';
                $suggested_reason = 'This URL is already covered by a 410 rule and should stay removed.';
                $seo_priority = 'medium';
                $approval_state = 'applied';
                $history_signals[] = 'existing_410_rule';
                $gone_candidates++;
            } elseif (isset($redirect_targets[$source_url])) {
                $suggested_action = 'redirect';
                $best_redirect_url = (string) ($redirect_targets[$source_url]['target_url'] ?? $best_redirect_url);
                $best_redirect_confidence = 100;
                $suggested_reason = 'This 404 already has an active 301 rule configured.';
                $approval_state = 'applied';
                $history_signals[] = 'existing_301_rule';
                $redirect_ready++;
            } elseif ($best_redirect_url !== '' && $best_redirect_confidence >= 90) {
                $suggested_action = 'redirect';
                $suggested_reason = 'High-confidence replacement found based on URL semantics and local history.';
                $redirect_ready++;
                $history_signals[] = 'semantic_match';
            } elseif ($best_redirect_url === '' && intval($row['hits'] ?? 0) <= 1 && (string) ($row['source'] ?? '') === 'site_audit') {
                $suggested_action = 'gone';
                $suggested_reason = 'No relevant replacement found and the URL looks removable rather than redirectable.';
                $gone_candidates++;
                $history_signals[] = 'no_replacement';
            } else {
                $review_required++;
                if ($best_redirect_url !== '') {
                    $history_signals[] = 'weak_match';
                    $suggested_reason = 'A possible replacement exists, but confidence is not high enough for auto-redirect.';
                } else {
                    $history_signals[] = 'manual_review';
                }
            }

            if ($best_redirect_url !== '') {
                $normalized_target = $best_redirect_url;
                if (!preg_match('#^https?://#i', $normalized_target)) {
                    $normalized_target = $this->aysa_normalize_redirect_path($normalized_target);
                }
                if ($normalized_target === $source_url) {
                    $conflict_flags[] = 'self_redirect';
                }
                if (isset($gone_urls[$normalized_target])) {
                    $conflict_flags[] = 'target_is_410';
                }
                if (isset($redirect_targets[$normalized_target])) {
                    $conflict_flags[] = 'target_has_redirect';
                }
            }

            if (isset($gone_urls[$source_url]) && isset($redirect_targets[$source_url])) {
                $conflict_flags[] = 'redirect_and_gone';
            }

            if ($suggested_action === 'redirect' && !empty($conflict_flags)) {
                $suggested_action = 'review';
                $suggested_reason = 'Suggested redirect has unresolved conflicts and needs manual review.';
                $review_required++;
                $redirect_ready = max(0, $redirect_ready - 1);
            }

            if (!empty($conflict_flags)) {
                $conflicts[] = array(
                    'source_url' => $source_url,
                    'flags' => array_values(array_unique($conflict_flags)),
                );
            }

            $rows[] = array(
                'kind' => '404',
                'source_url' => $source_url,
                'target_url' => '',
                'status' => '404',
                'origin' => (string) ($row['source'] ?? 'tracker'),
                'hits' => intval($row['hits'] ?? 0),
                'last_seen' => sanitize_text_field((string) ($row['last_hit'] ?? '')),
                'seo_priority' => $seo_priority,
                'suggested_action' => $suggested_action,
                'suggested_target_url' => $best_redirect_url,
                'suggested_confidence' => $best_redirect_confidence,
                'suggested_reason' => $suggested_reason,
                'history_signals' => array_values(array_unique($history_signals)),
                'approval_state' => $approval_state,
                'conflict_flags' => array_values(array_unique($conflict_flags)),
            );
        }

        foreach ($rows as &$row) {
            if ($row['kind'] === '301' && isset($gone_urls[$row['source_url']])) {
                $row['conflict_flags'][] = 'redirect_and_gone';
                $conflicts[] = array(
                    'source_url' => $row['source_url'],
                    'flags' => array('redirect_and_gone'),
                );
            }
            if ($row['kind'] === '301' && $row['target_url'] !== '') {
                $normalized_target = preg_match('#^https?://#i', $row['target_url'])
                    ? $row['target_url']
                    : $this->aysa_normalize_redirect_path($row['target_url']);
                if (isset($gone_urls[$normalized_target])) {
                    $row['conflict_flags'][] = 'target_is_410';
                    $conflicts[] = array(
                        'source_url' => $row['source_url'],
                        'flags' => array('target_is_410'),
                    );
                }
            }
            $row['conflict_flags'] = array_values(array_unique(array_filter((array) $row['conflict_flags'])));
        }
        unset($row);

        $bulk_candidates = array(
            'redirect' => array_values(array_filter($rows, static function ($row) {
                return is_array($row)
                    && ($row['kind'] ?? '') === '404'
                    && ($row['suggested_action'] ?? '') === 'redirect'
                    && intval($row['suggested_confidence'] ?? 0) >= 90
                    && empty($row['conflict_flags']);
            })),
            'gone' => array_values(array_filter($rows, static function ($row) {
                return is_array($row)
                    && ($row['kind'] ?? '') === '404'
                    && ($row['suggested_action'] ?? '') === 'gone'
                    && empty($row['conflict_flags']);
            })),
        );

        usort($rows, static function ($a, $b) {
            $priority_rank = array('high' => 3, 'medium' => 2, 'low' => 1);
            $a_rank = intval($priority_rank[(string) ($a['seo_priority'] ?? 'low')] ?? 0);
            $b_rank = intval($priority_rank[(string) ($b['seo_priority'] ?? 'low')] ?? 0);
            if ($a_rank !== $b_rank) {
                return $b_rank <=> $a_rank;
            }
            return strcmp((string) ($b['last_seen'] ?? ''), (string) ($a['last_seen'] ?? ''));
        });

        $summary = array(
            'total_404' => intval($technical_404_errors['count'] ?? 0),
            'total_301' => intval($technical_301_redirects['count'] ?? 0),
            'total_410' => intval($technical_410_gone['count'] ?? 0),
            'redirect_ready' => count($bulk_candidates['redirect']),
            'gone_candidates' => count($bulk_candidates['gone']),
            'review_required' => intval($review_required),
            'ignored_noise' => intval($noise_count),
            'pending_reviews' => count($pending_review_rows),
            'conflicts' => count($conflicts),
        );

        return array(
            'id' => 'technical_errors',
            'status' => !empty($rows) ? 'ready' : 'idle',
            'count' => count($rows),
            'summary' => $summary,
            'rows' => array_values($rows),
            'pending_reviews' => array_values($pending_review_rows),
            'conflicts' => array_values($conflicts),
            'bulk_candidates' => $bulk_candidates,
            'recent_actions' => array(),
            'has_data' => !empty($rows),
        );
    }

    private function aysa_adjustments_get_cached_or_idle_technical_errors_snapshot($project_id) {
        $project_id = intval($project_id);
        $state = get_option($this->aysa_technical_redirects_state_key($project_id), array());
        if (!is_array($state)) {
            $state = array();
        }

        $snapshot = isset($state['snapshot']) && is_array($state['snapshot']) ? $state['snapshot'] : array();
        $status = sanitize_key((string) ($state['status'] ?? 'idle'));
        $run_id = intval($state['run_id'] ?? 0);

        if ($snapshot !== array()) {
            $snapshot['status'] = $status !== '' ? $status : (string) ($snapshot['status'] ?? 'ready');
            if ($run_id > 0) {
                $snapshot['run_id'] = $run_id;
            }
            return $snapshot;
        }

        if (in_array($status, array('queued', 'running', 'failed', 'blocked'), true)) {
            return array(
                'id' => 'technical_errors',
                'status' => $status,
                'run_id' => $run_id,
                'count' => 0,
                'summary' => array(
                    'total_404' => 0,
                    'total_301' => 0,
                    'total_410' => 0,
                    'redirect_ready' => 0,
                    'gone_candidates' => 0,
                    'review_required' => 0,
                    'ignored_noise' => 0,
                    'pending_reviews' => 0,
                    'conflicts' => 0,
                ),
                'rows' => array(),
                'pending_reviews' => array(),
                'conflicts' => array(),
                'bulk_candidates' => array('redirect' => array(), 'gone' => array()),
                'recent_actions' => array(),
                'has_data' => false,
            );
        }

        $live = $this->aysa_adjustments_get_technical_errors_snapshot($project_id);
        if ($run_id > 0 && is_array($live)) {
            $live['run_id'] = $run_id;
        }
        return $live;
    }

    private function aysa_adjustments_normalize_schema_url($url) {
        $url = trim((string) $url);
        if ($url === '') {
            return '';
        }
        $url = esc_url_raw($url);
        if ($url === '') {
            return '';
        }
        $parts = wp_parse_url($url);
        if (!$parts || empty($parts['host'])) {
            return untrailingslashit($url) . '/';
        }
        $scheme = isset($parts['scheme']) ? strtolower((string) $parts['scheme']) : (is_ssl() ? 'https' : 'http');
        $host = strtolower((string) $parts['host']);
        $port = isset($parts['port']) ? ':' . intval($parts['port']) : '';
        $path = isset($parts['path']) ? (string) $parts['path'] : '/';
        $path = '/' . ltrim($path, '/');
        if ($path === '//') {
            $path = '/';
        }
        if (substr($path, -1) !== '/') {
            $path .= '/';
        }
        return $scheme . '://' . $host . $port . $path;
    }

    private function aysa_adjustments_get_schema_cache_key($project_id, $suffix = 'snapshot') {
        return 'aysa_technical_schema_' . sanitize_key((string) $suffix) . '_' . sanitize_key((string) $project_id) . '_v3';
    }

    private function aysa_technical_schema_resolve_project_id($project_id) {
        return $this->aysa_resolve_technical_project_id(intval($project_id));
    }

    private function aysa_schema_inventory_table() {
        global $wpdb;
        return $wpdb->prefix . 'aysa_schema_inventory';
    }

    private function aysa_schema_inventory_schema_version() {
        return '1.0.0';
    }

    private function aysa_schema_inventory_schema_version_option() {
        return 'aysa_schema_inventory_schema_version';
    }

    private function aysa_schema_inventory_delta_queue_option_key() {
        return 'aysa_schema_inventory_delta_queue_v1';
    }

    private function aysa_schema_inventory_context_key($project_id) {
        return 'aysa_schema_inventory_context_' . intval($project_id);
    }

    private function aysa_schema_inventory_override_key($project_id) {
        return 'aysa_schema_inventory_overrides_' . intval($project_id);
    }

    public function aysa_schema_inventory_maybe_upgrade_table() {
        $current = (string) get_option($this->aysa_schema_inventory_schema_version_option(), '');
        if ($current === $this->aysa_schema_inventory_schema_version()) {
            return;
        }

        global $wpdb;
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';

        $table = esc_sql($this->aysa_schema_inventory_table());
        $charset_collate = $wpdb->get_charset_collate();
        $sql = "CREATE TABLE {$table} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            project_id bigint(20) unsigned NOT NULL,
            url_normalized varchar(500) NOT NULL,
            title text DEFAULT NULL,
            object_id bigint(20) unsigned DEFAULT NULL,
            object_kind varchar(30) NOT NULL DEFAULT 'unknown',
            post_type varchar(80) DEFAULT NULL,
            taxonomy varchar(80) DEFAULT NULL,
            page_family varchar(40) NOT NULL DEFAULT 'page',
            entity_group varchar(40) NOT NULL DEFAULT 'page',
            google_policy_type varchar(60) NOT NULL DEFAULT 'generic_inventory_only',
            google_supported tinyint(1) NOT NULL DEFAULT 0,
            generation_mode varchar(40) NOT NULL DEFAULT 'inventory_only',
            not_applicable_reason text DEFAULT NULL,
            discovery_status varchar(30) NOT NULL DEFAULT 'discovered',
            generation_status varchar(40) NOT NULL DEFAULT 'inventory_only',
            google_readiness_status varchar(40) NOT NULL DEFAULT 'not_applicable',
            needs_takeover tinyint(1) NOT NULL DEFAULT 0,
            needs_fields longtext DEFAULT NULL,
            generated_by_aysa tinyint(1) NOT NULL DEFAULT 0,
            has_conflict tinyint(1) NOT NULL DEFAULT 0,
            content_fingerprint varchar(64) DEFAULT NULL,
            source_last_modified_at datetime DEFAULT NULL,
            last_generated_at datetime DEFAULT NULL,
            last_validated_at datetime DEFAULT NULL,
            last_seen_at datetime DEFAULT NULL,
            last_error text DEFAULT NULL,
            payload_snapshot_json longtext DEFAULT NULL,
            created_at datetime NOT NULL,
            updated_at datetime NOT NULL,
            PRIMARY KEY  (id),
            UNIQUE KEY project_url (project_id, url_normalized),
            KEY project_group (project_id, entity_group),
            KEY project_policy (project_id, google_policy_type),
            KEY project_generated (project_id, generated_by_aysa),
            KEY project_readiness (project_id, google_readiness_status),
            KEY object_lookup (object_kind, object_id)
        ) {$charset_collate};";

        dbDelta($sql);
        update_option($this->aysa_schema_inventory_schema_version_option(), $this->aysa_schema_inventory_schema_version(), false);
    }

    public function aysa_schema_inventory_ensure_scheduled_reconcile() {
        if (!wp_next_scheduled('aysa_schema_inventory_nightly_reconcile_event')) {
            wp_schedule_event(time() + HOUR_IN_SECONDS, 'daily', 'aysa_schema_inventory_nightly_reconcile_event');
        }
    }

    private function aysa_schema_inventory_policy_catalog() {
        return array(
            'organization_website' => array(
                'google_policy_type' => 'organization_website',
                'google_supported' => true,
                'generation_mode' => 'generate_or_validate',
                'required_fields' => array('name', 'url', 'logo'),
                'recommended_fields' => array('sameAs', 'contactPoint'),
                'not_applicable_reason' => '',
                'label' => 'Organization + WebSite',
            ),
            'local_business' => array(
                'google_policy_type' => 'local_business',
                'google_supported' => true,
                'generation_mode' => 'generate_or_validate',
                'required_fields' => array('name', 'address', 'telephone'),
                'recommended_fields' => array('openingHours', 'sameAs'),
                'not_applicable_reason' => '',
                'label' => 'LocalBusiness',
            ),
            'product_offer' => array(
                'google_policy_type' => 'product_offer',
                'google_supported' => true,
                'generation_mode' => 'generate_or_validate',
                'required_fields' => array('name', 'offers.price', 'offers.availability'),
                'recommended_fields' => array('image', 'sku', 'brand'),
                'not_applicable_reason' => '',
                'label' => 'Product + Offer',
            ),
            'article' => array(
                'google_policy_type' => 'article',
                'google_supported' => true,
                'generation_mode' => 'generate_or_validate',
                'required_fields' => array('headline', 'image', 'author', 'datePublished', 'dateModified'),
                'recommended_fields' => array('publisher'),
                'not_applicable_reason' => '',
                'label' => 'Article / BlogPosting',
            ),
            'faq_page' => array(
                'google_policy_type' => 'faq_page',
                'google_supported' => true,
                'generation_mode' => 'generate_or_validate',
                'required_fields' => array('mainEntity'),
                'recommended_fields' => array(),
                'not_applicable_reason' => '',
                'label' => 'FAQPage',
            ),
            'generic_inventory_only' => array(
                'google_policy_type' => 'generic_inventory_only',
                'google_supported' => false,
                'generation_mode' => 'inventory_only',
                'required_fields' => array(),
                'recommended_fields' => array(),
                'not_applicable_reason' => 'No primary Google rich result target is defined for this page type in V2.',
                'label' => 'Inventory only',
            ),
        );
    }

    private function aysa_schema_inventory_get_overrides($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array();
        }

        $overrides = get_option($this->aysa_schema_inventory_override_key($project_id), array());
        return is_array($overrides) ? $overrides : array();
    }

    private function aysa_schema_inventory_save_overrides($project_id, array $overrides) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return;
        }

        update_option($this->aysa_schema_inventory_override_key($project_id), $overrides, false);
    }

    private function aysa_schema_inventory_get_override($project_id, $url) {
        $url = $this->aysa_adjustments_normalize_schema_url((string) $url);
        if ($url === '') {
            return array();
        }

        $overrides = $this->aysa_schema_inventory_get_overrides($project_id);
        $override = $overrides[$url] ?? array();
        return is_array($override) ? $override : array();
    }

    private function aysa_schema_inventory_set_override($project_id, $url, array $override) {
        $project_id = intval($project_id);
        $url = $this->aysa_adjustments_normalize_schema_url((string) $url);
        if ($project_id <= 0 || $url === '') {
            return;
        }

        $overrides = $this->aysa_schema_inventory_get_overrides($project_id);
        $policy_override = sanitize_key((string) ($override['policy_override'] ?? ''));
        $inventory_only = !empty($override['inventory_only']);

        if ($policy_override === '' && !$inventory_only) {
            unset($overrides[$url]);
        } else {
            $overrides[$url] = array(
                'policy_override' => $policy_override,
                'inventory_only' => $inventory_only ? 1 : 0,
            );
        }

        $this->aysa_schema_inventory_save_overrides($project_id, $overrides);
    }

    private function aysa_schema_inventory_enqueue_delta(array $payload) {
        $queue = get_option($this->aysa_schema_inventory_delta_queue_option_key(), array());
        if (!is_array($queue)) {
            $queue = array();
        }
        $key = sanitize_key((string) ($payload['kind'] ?? '')) . ':' . sanitize_key((string) ($payload['taxonomy'] ?? '')) . ':' . intval($payload['id'] ?? 0);
        if ($key === '::0') {
            return;
        }
        $payload['queued_at'] = time();
        $queue[$key] = $payload;
        update_option($this->aysa_schema_inventory_delta_queue_option_key(), $queue, false);
        if (!wp_next_scheduled('aysa_schema_inventory_process_delta_event')) {
            wp_schedule_single_event(time() + 15, 'aysa_schema_inventory_process_delta_event');
        }
    }

    public function handle_schema_inventory_post_change($post_id, $post, $update) {
        if (!($post instanceof WP_Post)) {
            return;
        }
        if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
            return;
        }
        $this->aysa_schema_inventory_enqueue_delta(array(
            'kind' => 'post',
            'id' => intval($post_id),
            'action' => 'upsert',
        ));
    }

    public function handle_schema_inventory_post_status_transition($new_status, $old_status, $post) {
        if (!($post instanceof WP_Post)) {
            return;
        }
        if ($new_status === $old_status) {
            return;
        }
        $this->aysa_schema_inventory_enqueue_delta(array(
            'kind' => 'post',
            'id' => intval($post->ID),
            'action' => $new_status === 'publish' ? 'upsert' : 'delete',
        ));
    }

    public function handle_schema_inventory_post_delete($post_id) {
        $this->aysa_schema_inventory_enqueue_delta(array(
            'kind' => 'post',
            'id' => intval($post_id),
            'action' => 'delete',
        ));
    }

    public function handle_schema_inventory_term_change($term_id, $tt_id = 0, $taxonomy = '') {
        $taxonomy = sanitize_key((string) $taxonomy);
        if ($taxonomy === '') {
            return;
        }
        $this->aysa_schema_inventory_enqueue_delta(array(
            'kind' => 'term',
            'id' => intval($term_id),
            'taxonomy' => $taxonomy,
            'action' => 'upsert',
        ));
    }

    public function handle_schema_inventory_term_delete($term_id, $tt_id = 0, $taxonomy = '', $deleted_term = null, $object_ids = array()) {
        $taxonomy = sanitize_key((string) $taxonomy);
        if ($taxonomy === '') {
            return;
        }
        $this->aysa_schema_inventory_enqueue_delta(array(
            'kind' => 'term',
            'id' => intval($term_id),
            'taxonomy' => $taxonomy,
            'action' => 'delete',
        ));
    }

    public function handle_schema_inventory_delta_event() {
        $queue = get_option($this->aysa_schema_inventory_delta_queue_option_key(), array());
        if (!is_array($queue) || empty($queue)) {
            return;
        }

        $project_id = $this->aysa_technical_schema_resolve_project_id(0);
        if ($project_id <= 0) {
            return;
        }
        $processed = 0;
        foreach ($queue as $key => $payload) {
            if ($processed >= 20) {
                break;
            }
            $processed++;
            unset($queue[$key]);
            $action = sanitize_key((string) ($payload['action'] ?? 'upsert'));
            if ($action === 'delete') {
                $this->aysa_schema_inventory_delete_by_reference($project_id, $payload);
                continue;
            }
            $page_data = $this->aysa_schema_inventory_resolve_page_data_from_reference($payload);
            if (!is_array($page_data)) {
                $this->aysa_schema_inventory_delete_by_reference($project_id, $payload);
                continue;
            }
            $legacy_row = $this->aysa_adjustments_build_schema_row($page_data, $this->aysa_adjustments_get_schema_db_lookup(), true);
            $this->aysa_schema_inventory_upsert_row($project_id, $page_data, $legacy_row);
        }

        update_option($this->aysa_schema_inventory_delta_queue_option_key(), $queue, false);
        $this->aysa_schema_inventory_update_client_context($project_id);
        if (!empty($queue) && !wp_next_scheduled('aysa_schema_inventory_process_delta_event')) {
            wp_schedule_single_event(time() + 20, 'aysa_schema_inventory_process_delta_event');
        }
    }

    public function handle_schema_inventory_nightly_reconcile_event() {
        $project_id = $this->aysa_technical_schema_resolve_project_id(0);
        if ($project_id <= 0) {
            return;
        }
        $running_job = $this->aysa_technical_schema_get_current_refresh_job($project_id);
        if ($running_job && in_array((string) ($running_job['status'] ?? ''), array('queued', 'running'), true)) {
            return;
        }

        $items = array();
        foreach ((array) $this->aysa_adjustments_collect_schema_scope_pages() as $page_data) {
            if (!is_array($page_data)) {
                continue;
            }
            $url = $this->aysa_adjustments_normalize_schema_url((string) ($page_data['url'] ?? ''));
            if ($url === '') {
                continue;
            }
            $page_data['url'] = $url;
            $items[$url] = $page_data;
        }
        if (empty($items)) {
            return;
        }

        $job = $this->aysa_technical_schema_create_job($project_id, 'reconcile_inventory', 'sitewide', array_values($items), 'snapshot_refresh');
        set_transient($this->aysa_technical_schema_refresh_job_pointer_key($project_id), (string) $job['job_id'], 12 * HOUR_IN_SECONDS);
        $this->aysa_technical_schema_schedule_job((string) $job['job_id'], 1);
    }

    private function aysa_schema_inventory_resolve_page_data_from_reference(array $payload) {
        $kind = sanitize_key((string) ($payload['kind'] ?? ''));
        $id = intval($payload['id'] ?? 0);
        if ($id <= 0) {
            return null;
        }

        if ($kind === 'post') {
            $post = get_post($id);
            if (!($post instanceof WP_Post) || $post->post_status !== 'publish') {
                return null;
            }
            $post_type_object = get_post_type_object($post->post_type);
            if (!($post_type_object instanceof WP_Post_Type) || empty($post_type_object->public)) {
                return null;
            }
            if (in_array($post->post_type, array('attachment', 'revision', 'nav_menu_item', 'custom_css', 'customize_changeset'), true)) {
                return null;
            }
            $page_family = 'custom';
            if ($post->post_type === 'post') {
                $page_family = 'article';
            } elseif ($post->post_type === 'page') {
                $page_family = 'page';
            } elseif ($post->post_type === 'product') {
                $page_family = 'product';
            }
            $page_data = array(
                'url' => get_permalink($post),
                'title' => get_the_title($post),
                'page_family' => $page_family,
                'object_id' => intval($post->ID),
                'object_kind' => 'post',
                'post_type' => $post->post_type,
            );
            $page_data['entity_group'] = $this->aysa_technical_schema_resolve_entity_group($page_data);
            return $page_data;
        }

        if ($kind === 'term') {
            $taxonomy = sanitize_key((string) ($payload['taxonomy'] ?? ''));
            $taxonomy_object = $taxonomy !== '' ? get_taxonomy($taxonomy) : null;
            if (!($taxonomy_object instanceof WP_Taxonomy) || empty($taxonomy_object->public)) {
                return null;
            }
            if (in_array($taxonomy, array('post_format', 'nav_menu', 'link_category', 'product_shipping_class'), true)) {
                return null;
            }
            $term = get_term($id, $taxonomy);
            if (!($term instanceof WP_Term) || is_wp_error($term)) {
                return null;
            }
            $url = get_term_link($term);
            if (is_wp_error($url)) {
                return null;
            }
            $page_family = 'custom';
            if ($taxonomy === 'category' || $taxonomy === 'product_cat' || $taxonomy === 'product_tag') {
                $page_family = 'category';
            } elseif ($taxonomy === 'post_tag') {
                $page_family = 'tag';
            }
            $page_data = array(
                'url' => $url,
                'title' => $term->name,
                'page_family' => $page_family,
                'object_id' => intval($term->term_id),
                'object_kind' => 'term',
                'taxonomy' => $taxonomy,
            );
            $page_data['entity_group'] = $this->aysa_technical_schema_resolve_entity_group($page_data);
            return $page_data;
        }

        return null;
    }

    private function aysa_schema_inventory_delete_by_reference($project_id, array $payload) {
        global $wpdb;
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return;
        }
        $table = esc_sql($this->aysa_schema_inventory_table());
        $kind = sanitize_key((string) ($payload['kind'] ?? ''));
        $id = intval($payload['id'] ?? 0);
        if ($kind === 'post' || $kind === 'term') {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->delete($table, array(
                'project_id' => $project_id,
                'object_kind' => $kind,
                'object_id' => $id,
            ), array('%d', '%s', '%d'));
        }
    }

    private function aysa_schema_inventory_determine_google_policy(array $page_data, array $legacy_row = array()) {
        $entity_group = sanitize_key((string) ($page_data['entity_group'] ?? 'page'));
        $page_family = sanitize_key((string) ($page_data['page_family'] ?? 'page'));
        $title = strtolower(trim((string) ($page_data['title'] ?? '')));
        $url = strtolower(trim((string) ($page_data['url'] ?? '')));
        $catalog = $this->aysa_schema_inventory_policy_catalog();
        $policy = $catalog['generic_inventory_only'];

        if ($page_family === 'homepage' || $entity_group === 'business') {
            $business_like = $page_family === 'homepage'
                || strpos($url, '/contact') !== false
                || strpos($url, '/despre') !== false
                || strpos($url, '/about') !== false
                || strpos($url, '/location') !== false
                || strpos($url, '/showroom') !== false
                || strpos($title, 'contact') !== false
                || strpos($title, 'showroom') !== false
                || strpos($title, 'about') !== false;

            if ($page_family === 'homepage') {
                $policy = $catalog['organization_website'];
            } elseif ($business_like) {
                $has_local_data = !empty($legacy_row['business_source']) && !empty($legacy_row['nap_status']) && (string) ($legacy_row['nap_status'] ?? '') === 'ready';
                $policy = $has_local_data
                    ? $catalog['local_business']
                    : $catalog['generic_inventory_only'];
                if (!$has_local_data) {
                    $policy['not_applicable_reason'] = 'Business page is missing verified local business details.';
                }
            }
        } elseif ($entity_group === 'product') {
            $policy = $catalog['product_offer'];
        } elseif ($entity_group === 'article') {
            $policy = $catalog['article'];
        } elseif (!empty($legacy_row['primary_recommended_node']) && (string) $legacy_row['primary_recommended_node'] === 'faqpage') {
            $policy = $catalog['faq_page'];
        }

        $project_id = intval($page_data['project_id'] ?? 0);
        $override = $this->aysa_schema_inventory_get_override($project_id, (string) ($page_data['url'] ?? ''));
        $override_policy = sanitize_key((string) ($override['policy_override'] ?? ''));
        $inventory_only = !empty($override['inventory_only']);
        if ($inventory_only) {
            $policy = $catalog['generic_inventory_only'];
            $policy['not_applicable_reason'] = 'Manually set to inventory-only.';
        } elseif ($override_policy !== '' && isset($catalog[$override_policy])) {
            $policy = $catalog[$override_policy];
        }

        return $policy;
    }

    private function aysa_schema_inventory_build_entry($project_id, array $page_data, array $legacy_row = array()) {
        $project_id = intval($project_id);
        $url = $this->aysa_adjustments_normalize_schema_url((string) ($page_data['url'] ?? ''));
        $page_data['project_id'] = $project_id;
        $payload = $this->aysa_schema_inventory_determine_google_policy($page_data, $legacy_row);
        $google_supported = !empty($payload['google_supported']);
        $google_status = sanitize_key((string) ($legacy_row['google_status'] ?? ($google_supported ? 'missing_target' : 'not_applicable')));
        $status = sanitize_key((string) ($legacy_row['status'] ?? ($google_supported ? 'missing' : 'inventory_only')));
        $execution = sanitize_key((string) ($legacy_row['execution_eligibility'] ?? 'manual_review'));
        $generated_by_aysa = !empty($legacy_row['is_aysa_present']);
        $has_conflict = !empty($legacy_row['has_conflict']);

        $generation_status = 'inventory_only';
        if ($generated_by_aysa) {
            $generation_status = 'generated_by_aysa';
        } elseif (!$google_supported) {
            $generation_status = 'inventory_only';
        } elseif ($google_status === 'needs_fields') {
            $generation_status = 'needs_fields';
        } elseif ($google_status === 'needs_takeover' || $has_conflict) {
            $generation_status = 'needs_takeover';
        } elseif (in_array($execution, array('generate_safe', 'rewrite_existing_aysa', 'rewrite_takeover'), true)) {
            $generation_status = 'eligible';
        } else {
            $generation_status = 'pending_validation';
        }

        $google_readiness_status = 'not_applicable';
        if ($google_supported) {
            if ($google_status === 'ready') {
                $google_readiness_status = 'ready';
            } elseif ($google_status === 'needs_fields') {
                $google_readiness_status = 'needs_fields';
            } elseif ($has_conflict || $google_status === 'conflict') {
                $google_readiness_status = 'validation_failed';
            } else {
                $google_readiness_status = 'needs_action';
            }
        }

        $modified_gmt = null;
        if (!empty($page_data['post_type']) && !empty($page_data['object_id'])) {
            $post = get_post(intval($page_data['object_id']));
            if ($post instanceof WP_Post) {
                $modified_gmt = $post->post_modified_gmt && $post->post_modified_gmt !== '0000-00-00 00:00:00'
                    ? get_date_from_gmt($post->post_modified_gmt, 'Y-m-d H:i:s')
                    : null;
            }
        }
        if ($modified_gmt === null && !empty($page_data['taxonomy']) && !empty($page_data['object_id'])) {
            $modified_gmt = current_time('mysql');
        }

        $fingerprint = md5(wp_json_encode(array(
            'title' => (string) ($page_data['title'] ?? ''),
            'page_family' => (string) ($page_data['page_family'] ?? ''),
            'entity_group' => (string) ($page_data['entity_group'] ?? ''),
            'status' => $status,
            'google_status' => $google_status,
            'generated_by_aysa' => $generated_by_aysa ? 1 : 0,
        )));

        return array(
            'project_id' => $project_id,
            'url_normalized' => $url,
            'title' => sanitize_text_field((string) ($page_data['title'] ?? $url)),
            'object_id' => intval($page_data['object_id'] ?? 0),
            'object_kind' => sanitize_key((string) ($page_data['object_kind'] ?? (!empty($page_data['taxonomy']) ? 'term' : 'post'))),
            'post_type' => sanitize_key((string) ($page_data['post_type'] ?? '')),
            'taxonomy' => sanitize_key((string) ($page_data['taxonomy'] ?? '')),
            'page_family' => sanitize_key((string) ($page_data['page_family'] ?? 'page')),
            'entity_group' => sanitize_key((string) ($page_data['entity_group'] ?? 'page')),
            'google_policy_type' => sanitize_key((string) ($payload['google_policy_type'] ?? 'generic_inventory_only')),
            'google_supported' => $google_supported ? 1 : 0,
            'generation_mode' => sanitize_key((string) ($payload['generation_mode'] ?? 'inventory_only')),
            'not_applicable_reason' => sanitize_text_field((string) ($payload['not_applicable_reason'] ?? '')),
            'discovery_status' => 'discovered',
            'generation_status' => $generation_status,
            'google_readiness_status' => $google_readiness_status,
            'needs_takeover' => ($google_status === 'needs_takeover' || $has_conflict) ? 1 : 0,
            'needs_fields' => wp_json_encode(array_values((array) ($legacy_row['google_required_missing'] ?? $legacy_row['required_fields_missing'] ?? array()))),
            'generated_by_aysa' => $generated_by_aysa ? 1 : 0,
            'has_conflict' => $has_conflict ? 1 : 0,
            'content_fingerprint' => $fingerprint,
            'source_last_modified_at' => $modified_gmt,
            'last_generated_at' => $generated_by_aysa ? current_time('mysql') : null,
            'last_validated_at' => current_time('mysql'),
            'last_seen_at' => current_time('mysql'),
            'last_error' => sanitize_text_field((string) ($legacy_row['google_reason'] ?? '')),
            'payload_snapshot_json' => wp_json_encode(array(
                'page_data' => $page_data,
                'legacy_row' => $legacy_row,
                'policy' => $payload,
            )),
            'status_label' => sanitize_text_field((string) ($legacy_row['google_target_label'] ?? '')),
        );
    }

    private function aysa_schema_inventory_upsert_row($project_id, array $page_data, array $legacy_row = array()) {
        global $wpdb;
        $entry = $this->aysa_schema_inventory_build_entry($project_id, $page_data, $legacy_row);
        if (empty($entry['url_normalized'])) {
            return;
        }
        $table = esc_sql($this->aysa_schema_inventory_table());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $existing_id = $wpdb->get_var($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT id FROM {$table} WHERE project_id = %d AND url_normalized = %s LIMIT 1",
            intval($entry['project_id']),
            (string) $entry['url_normalized']
        ));

        $data = array(
            'project_id' => intval($entry['project_id']),
            'url_normalized' => (string) $entry['url_normalized'],
            'title' => (string) $entry['title'],
            'object_id' => intval($entry['object_id']),
            'object_kind' => (string) $entry['object_kind'],
            'post_type' => (string) $entry['post_type'],
            'taxonomy' => (string) $entry['taxonomy'],
            'page_family' => (string) $entry['page_family'],
            'entity_group' => (string) $entry['entity_group'],
            'google_policy_type' => (string) $entry['google_policy_type'],
            'google_supported' => intval($entry['google_supported']),
            'generation_mode' => (string) $entry['generation_mode'],
            'not_applicable_reason' => (string) $entry['not_applicable_reason'],
            'discovery_status' => (string) $entry['discovery_status'],
            'generation_status' => (string) $entry['generation_status'],
            'google_readiness_status' => (string) $entry['google_readiness_status'],
            'needs_takeover' => intval($entry['needs_takeover']),
            'needs_fields' => (string) $entry['needs_fields'],
            'generated_by_aysa' => intval($entry['generated_by_aysa']),
            'has_conflict' => intval($entry['has_conflict']),
            'content_fingerprint' => (string) $entry['content_fingerprint'],
            'source_last_modified_at' => $entry['source_last_modified_at'],
            'last_generated_at' => $entry['last_generated_at'],
            'last_validated_at' => $entry['last_validated_at'],
            'last_seen_at' => $entry['last_seen_at'],
            'last_error' => (string) $entry['last_error'],
            'payload_snapshot_json' => (string) $entry['payload_snapshot_json'],
            'updated_at' => current_time('mysql'),
        );

        if ($existing_id) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->update($table, $data, array('id' => intval($existing_id)));
        } else {
            $data['created_at'] = current_time('mysql');
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->insert($table, $data);
        }
    }

    private function aysa_schema_inventory_finalize_reconcile($project_id, array $seen_urls) {
        global $wpdb;
        $table = esc_sql($this->aysa_schema_inventory_table());
        $project_id = intval($project_id);
        $seen_urls = array_values(array_filter(array_map(array($this, 'aysa_adjustments_normalize_schema_url'), $seen_urls)));
        if ($project_id <= 0) {
            return;
        }
        if (empty($seen_urls)) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->delete($table, array('project_id' => $project_id), array('%d'));
            return;
        }

        $placeholders = implode(',', array_fill(0, count($seen_urls), '%s'));
        $params = array_merge(array($project_id), $seen_urls);
        $sql = $wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "DELETE FROM {$table} WHERE project_id = %d AND url_normalized NOT IN ({$placeholders})",
            $params
        );
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query: table identifiers and structure are internal, and request-derived values are prepared before execution. Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $wpdb->query($sql);
    }

    private function aysa_schema_inventory_fetch_rows($project_id) {
        global $wpdb;
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array();
        }
        $table = esc_sql($this->aysa_schema_inventory_table());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $rows = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT * FROM {$table} WHERE project_id = %d ORDER BY entity_group ASC, title ASC, url_normalized ASC",
            $project_id
        ), ARRAY_A);
        $rows = is_array($rows) ? $rows : array();
        $overrides = $this->aysa_schema_inventory_get_overrides($project_id);
        foreach ($rows as &$row) {
            $row['needs_fields'] = json_decode((string) ($row['needs_fields'] ?? '[]'), true);
            if (!is_array($row['needs_fields'])) {
                $row['needs_fields'] = array();
            }
            $payload = json_decode((string) ($row['payload_snapshot_json'] ?? '{}'), true);
            $row['payload_snapshot'] = is_array($payload) ? $payload : array();
            $override = $overrides[(string) ($row['url_normalized'] ?? '')] ?? array();
            $row['policy_override'] = sanitize_key((string) ($override['policy_override'] ?? ''));
            $row['inventory_only_override'] = !empty($override['inventory_only']) ? 1 : 0;
            $legacy = is_array($row['payload_snapshot']['legacy_row'] ?? null) ? $row['payload_snapshot']['legacy_row'] : array();
            if (!empty($row['has_conflict'])) {
                $row['ownership'] = 'mixed/conflict';
            } elseif (!empty($row['generated_by_aysa'])) {
                $row['ownership'] = 'aysa';
            } elseif (!empty($legacy['is_external_schema_present']) || !empty($row['needs_takeover'])) {
                $row['ownership'] = 'external';
            } else {
                $row['ownership'] = 'none';
            }
        }
        unset($row);
        return $rows;
    }

    private function aysa_schema_inventory_get_ui_snapshot($project_id) {
        $project_id = intval($project_id);
        $rows = $this->aysa_schema_inventory_fetch_rows($project_id);
        $summary = array(
            'inventory_total' => 0,
            'google_eligible_total' => 0,
            'needs_action_total' => 0,
            'generated_total' => 0,
            'conflicts_total' => 0,
            'google_ready_total' => 0,
            'inventory_only_total' => 0,
            'entity_groups' => array(),
        );
        $supported_groups = $this->aysa_technical_schema_supported_entity_groups();
        $group_rows = array_fill_keys($supported_groups, array());

        foreach ($rows as $row) {
            $summary['inventory_total']++;
            if (!empty($row['google_supported'])) {
                $summary['google_eligible_total']++;
            } else {
                $summary['inventory_only_total']++;
            }
            if (!empty($row['generated_by_aysa'])) {
                $summary['generated_total']++;
            }
            if ((string) ($row['google_readiness_status'] ?? '') === 'ready') {
                $summary['google_ready_total']++;
            }
            if (!empty($row['has_conflict'])) {
                $summary['conflicts_total']++;
            }
            $needs_action = !empty($row['google_supported']) && (
                !empty($row['needs_takeover'])
                || in_array((string) ($row['google_readiness_status'] ?? ''), array('needs_action', 'needs_fields', 'validation_failed'), true)
                || in_array((string) ($row['generation_status'] ?? ''), array('needs_takeover', 'needs_fields', 'pending_validation'), true)
            );
            if ($needs_action) {
                $summary['needs_action_total']++;
            }

            $group = sanitize_key((string) ($row['entity_group'] ?? 'page'));
            if (!isset($group_rows[$group])) {
                $group_rows[$group] = array();
            }
            $group_rows[$group][] = $row;
        }

        foreach ($group_rows as $group => $items) {
            $summary['entity_groups'][$group] = array(
                'label' => $this->aysa_technical_schema_entity_group_label($group),
                'total' => count($items),
                'generated' => count(array_filter($items, static function($row) {
                    return !empty($row['generated_by_aysa']);
                })),
                'google_ready' => count(array_filter($items, static function($row) {
                    return (string) ($row['google_readiness_status'] ?? '') === 'ready';
                })),
                'needs_action' => count(array_filter($items, static function($row) {
                    return !empty($row['google_supported']) && (
                        !empty($row['needs_takeover'])
                        || in_array((string) ($row['google_readiness_status'] ?? ''), array('needs_action', 'needs_fields', 'validation_failed'), true)
                        || in_array((string) ($row['generation_status'] ?? ''), array('needs_takeover', 'needs_fields', 'pending_validation'), true)
                    );
                })),
            );
        }

        $refresh_job = $this->aysa_technical_schema_get_current_refresh_job($project_id);
        $status = ($refresh_job && in_array((string) ($refresh_job['status'] ?? ''), array('queued', 'running'), true))
            ? 'reconciling'
            : 'ready';

        return array(
            'resolved_project_id' => $project_id,
            'status' => $status,
            'summary' => $summary,
            'rows' => $rows,
            'group_rows' => $group_rows,
            'needs_reconcile' => empty($rows),
            'job' => $refresh_job ? $this->aysa_technical_schema_get_job_response($refresh_job) : null,
            'updated_at' => current_time('mysql'),
        );
    }

    private function aysa_schema_inventory_update_client_context($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return;
        }
        $snapshot = $this->aysa_schema_inventory_get_ui_snapshot($project_id);
        update_option($this->aysa_schema_inventory_context_key($project_id), array(
            'project_id' => $project_id,
            'updated_at' => current_time('mysql'),
            'summary' => $snapshot['summary'],
            'recent_rows' => array_slice(array_values((array) $snapshot['rows']), 0, 20),
            'status' => $snapshot['status'],
        ), false);
    }

    public function handle_schema_inventory_summary() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        wp_send_json_success(array(
            'snapshot' => $this->aysa_schema_inventory_get_ui_snapshot($project_id),
        ));
    }

    public function handle_schema_inventory_list() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $filter = sanitize_key((string) wp_unslash($_POST['filter'] ?? 'inventory'));
        $rows = $this->aysa_schema_inventory_fetch_rows($project_id);
        $rows = array_values(array_filter($rows, static function($row) use ($filter) {
            switch ($filter) {
                case 'google_eligible':
                    return !empty($row['google_supported']);
                case 'needs_action':
                    return !empty($row['google_supported']) && (
                        !empty($row['needs_takeover'])
                        || in_array((string) ($row['google_readiness_status'] ?? ''), array('needs_action', 'needs_fields', 'validation_failed'), true)
                        || in_array((string) ($row['generation_status'] ?? ''), array('needs_takeover', 'needs_fields', 'pending_validation'), true)
                    );
                case 'generated':
                    return !empty($row['generated_by_aysa']);
                case 'conflicts':
                    return !empty($row['has_conflict']);
                case 'no_conflict':
                    return empty($row['has_conflict']);
                case 'page':
                case 'product':
                case 'ecommerce_category':
                case 'article':
                case 'business':
                    return sanitize_key((string) ($row['entity_group'] ?? 'page')) === $filter;
                default:
                    return true;
            }
        }));
        wp_send_json_success(array(
            'rows' => $rows,
        ));
    }

    private function aysa_schema_inventory_find_row($project_id, $url) {
        $url = $this->aysa_adjustments_normalize_schema_url((string) $url);
        if ($url === '') {
            return array();
        }

        foreach ($this->aysa_schema_inventory_fetch_rows($project_id) as $row) {
            if ((string) ($row['url_normalized'] ?? '') === $url) {
                return $row;
            }
        }

        return array();
    }

    private function aysa_schema_inventory_row_to_job_item(array $row) {
        $payload = is_array($row['payload_snapshot'] ?? null) ? $row['payload_snapshot'] : array();
        $legacy = is_array($payload['legacy_row'] ?? null) ? $payload['legacy_row'] : array();
        $schema_type = sanitize_key((string) ($legacy['primary_recommended_node'] ?? ''));
        if ($schema_type === '') {
            $policy_type = sanitize_key((string) ($row['google_policy_type'] ?? ''));
            $schema_type_map = array(
                'organization_website' => 'organization',
                'local_business' => 'localbusiness',
                'product_offer' => 'product',
                'article' => 'article',
                'faq_page' => 'faqpage',
            );
            $schema_type = $schema_type_map[$policy_type] ?? 'webpage';
        }

        return array(
            'url' => (string) ($row['url_normalized'] ?? ''),
            'title' => sanitize_text_field((string) ($row['title'] ?? '')),
            'page_family' => sanitize_key((string) ($row['page_family'] ?? 'page')),
            'entity_group' => sanitize_key((string) ($row['entity_group'] ?? 'page')),
            'schema_type' => $schema_type,
            'primary_recommended_node' => $schema_type,
        );
    }

    private function aysa_schema_inventory_get_row_detail($project_id, array $row) {
        $project_id = intval($project_id);
        if (empty($row)) {
            return array();
        }

        $payload = is_array($row['payload_snapshot'] ?? null) ? $row['payload_snapshot'] : array();
        $legacy = is_array($payload['legacy_row'] ?? null) ? $payload['legacy_row'] : array();
        $policy = is_array($payload['policy'] ?? null) ? $payload['policy'] : array();
        $schemas = class_exists('AysaSchemaManager')
            ? AysaSchemaManager::get_page_schemas((string) ($row['url_normalized'] ?? ''))
            : array();

        $current_schema_types = array_values(array_filter(array_unique(array_map(static function($schema) {
            return sanitize_key((string) ($schema['type'] ?? ''));
        }, is_array($schemas) ? $schemas : array()))));

        $detected_frontend_types = array_values(array_filter(array_unique(array_merge(
            array_map('sanitize_key', (array) ($legacy['detected_nodes'] ?? array())),
            array_map('sanitize_key', (array) ($legacy['generated_types'] ?? array()))
        ))));

        $available_actions = array('validate');
        if (!empty($row['google_supported']) && (string) ($row['generation_status'] ?? '') !== 'generated_by_aysa') {
            $available_actions[] = 'generate';
        }
        if (!empty($row['needs_takeover']) || !empty($row['generated_by_aysa']) || !empty($row['has_conflict'])) {
            $available_actions[] = 'rewrite';
        }
        $available_actions[] = 'disable';

        return array(
            'identity' => array(
                'url' => (string) ($row['url_normalized'] ?? ''),
                'title' => (string) ($row['title'] ?? ''),
                'entity_group' => (string) ($row['entity_group'] ?? 'page'),
                'entity_group_label' => $this->aysa_technical_schema_entity_group_label((string) ($row['entity_group'] ?? 'page')),
                'page_family' => (string) ($row['page_family'] ?? 'page'),
                'post_type' => (string) ($row['post_type'] ?? ''),
                'taxonomy' => (string) ($row['taxonomy'] ?? ''),
            ),
            'state' => array(
                'generation_status' => (string) ($row['generation_status'] ?? ''),
                'google_readiness_status' => (string) ($row['google_readiness_status'] ?? ''),
                'ownership' => (string) ($row['ownership'] ?? 'none'),
                'conflict' => !empty($row['has_conflict']),
                'conflict_reason' => sanitize_text_field((string) ($legacy['conflict_reason'] ?? $row['last_error'] ?? '')),
                'next_step' => sanitize_text_field((string) ($row['last_error'] ?? '')),
                'generated_by_aysa' => !empty($row['generated_by_aysa']),
                'google_supported' => !empty($row['google_supported']),
                'inventory_only_override' => !empty($row['inventory_only_override']),
                'policy_override' => (string) ($row['policy_override'] ?? ''),
            ),
            'policy' => array(
                'google_policy_type' => (string) ($row['google_policy_type'] ?? ''),
                'required_fields' => array_values((array) ($policy['required_fields'] ?? array())),
                'recommended_fields' => array_values((array) ($policy['recommended_fields'] ?? array())),
                'generation_mode' => (string) ($row['generation_mode'] ?? ''),
                'not_applicable_reason' => (string) ($row['not_applicable_reason'] ?? ''),
                'available_overrides' => $this->aysa_schema_inventory_policy_catalog(),
            ),
            'validation' => array(
                'needs_fields' => array_values((array) ($row['needs_fields'] ?? array())),
                'last_checked' => (string) ($row['last_validated_at'] ?? ''),
                'last_generated_at' => (string) ($row['last_generated_at'] ?? ''),
                'last_error' => (string) ($row['last_error'] ?? ''),
            ),
            'schemas' => array(
                'aysa_db_schemas' => is_array($schemas) ? $schemas : array(),
                'aysa_schema_types' => $current_schema_types,
                'frontend_detected_types' => $detected_frontend_types,
            ),
            'available_actions' => array_values(array_unique($available_actions)),
            'row' => $row,
        );
    }

    public function handle_schema_inventory_detail() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $url = isset($_POST['url']) ? (string) wp_unslash($_POST['url']) : '';
        $row = $this->aysa_schema_inventory_find_row($project_id, $url);
        if (empty($row)) {
            wp_send_json_error('Schema inventory row not found.');
            return;
        }

        wp_send_json_success(array(
            'detail' => $this->aysa_schema_inventory_get_row_detail($project_id, $row),
        ));
    }

    public function handle_schema_inventory_auto_generate() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        $rows = array_values(array_filter($this->aysa_schema_inventory_fetch_rows($project_id), static function($row) {
            return !empty($row['google_supported']) && (string) ($row['generation_mode'] ?? '') === 'generate_or_validate';
        }));

        if (empty($rows)) {
            wp_send_json_error('No Google-eligible schema rows are ready for automatic generation.');
            return;
        }

        $items = array_values(array_filter(array_map(array($this, 'aysa_schema_inventory_row_to_job_item'), $rows), static function($item) {
            return !empty($item['url']);
        }));
        if (empty($items)) {
            wp_send_json_error('Automatic generation could not build any valid schema items.');
            return;
        }

        $job = $this->aysa_technical_schema_create_job($project_id, 'full_sitewide_orchestrator', 'selection', $items, 'worker');
        $this->aysa_technical_schema_schedule_job((string) ($job['job_id'] ?? ''), 1);

        wp_send_json_success(array(
            'job' => $this->aysa_technical_schema_get_job_response((array) $job),
            'selected_count' => count($items),
        ));
    }

    public function handle_schema_inventory_row_action() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $url = isset($_POST['url']) ? (string) wp_unslash($_POST['url']) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $action = sanitize_key((string) wp_unslash($_POST['row_action'] ?? 'validate'));
        $row = $this->aysa_schema_inventory_find_row($project_id, $url);
        if (empty($row)) {
            wp_send_json_error('Schema inventory row not found.');
            return;
        }

        $mode_map = array(
            'generate' => 'generate_safe',
            'rewrite' => 'rewrite_recommended',
            'validate' => 'validate',
            'disable' => 'disable_selected',
        );
        $mode = $mode_map[$action] ?? 'validate';
        $job = $this->aysa_technical_schema_create_job(
            $project_id,
            $mode,
            'selection',
            array($this->aysa_schema_inventory_row_to_job_item($row)),
            'worker'
        );
        $this->aysa_technical_schema_schedule_job((string) ($job['job_id'] ?? ''), 1);

        wp_send_json_success(array(
            'job' => $this->aysa_technical_schema_get_job_response((array) $job),
        ));
    }

    public function handle_schema_inventory_row_update() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $url = isset($_POST['url']) ? (string) wp_unslash($_POST['url']) : '';
        $row = $this->aysa_schema_inventory_find_row($project_id, $url);
        if (empty($row)) {
            wp_send_json_error('Schema inventory row not found.');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $policy_override = sanitize_key((string) wp_unslash($_POST['policy_override'] ?? ''));
        $catalog = $this->aysa_schema_inventory_policy_catalog();
        if ($policy_override !== '' && !isset($catalog[$policy_override])) {
            wp_send_json_error('Invalid policy override.');
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $inventory_only = !empty($_POST['inventory_only']);
        $this->aysa_schema_inventory_set_override($project_id, $url, array(
            'policy_override' => $policy_override,
            'inventory_only' => $inventory_only,
        ));

        $payload = is_array($row['payload_snapshot'] ?? null) ? $row['payload_snapshot'] : array();
        $page_data = is_array($payload['page_data'] ?? null) ? $payload['page_data'] : array();
        $legacy_row = is_array($payload['legacy_row'] ?? null) ? $payload['legacy_row'] : array();
        if (!empty($page_data)) {
            $this->aysa_schema_inventory_upsert_row($project_id, $page_data, $legacy_row);
        }
        $this->aysa_schema_inventory_update_client_context($project_id);
        $updated_row = $this->aysa_schema_inventory_find_row($project_id, $url);

        wp_send_json_success(array(
            'detail' => $this->aysa_schema_inventory_get_row_detail($project_id, $updated_row),
        ));
    }

    private function aysa_adjustments_get_cached_technical_schema_snapshot($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return null;
        }

        $cached = get_transient($this->aysa_adjustments_get_schema_cache_key($project_id, 'snapshot'));
        return is_array($cached) ? $cached : null;
    }

    private function aysa_adjustments_get_cached_technical_schema_summary($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return null;
        }

        $cached = get_transient($this->aysa_adjustments_get_schema_cache_key($project_id, 'summary'));
        return is_array($cached) ? $cached : null;
    }

    private function aysa_adjustments_get_cached_or_idle_technical_schema_summary($project_id) {
        $summary = $this->aysa_adjustments_get_cached_technical_schema_summary($project_id);
        if (is_array($summary)) {
            return $summary;
        }

        return array(
            'status' => 'idle',
            'summary' => array(
                'total_pages' => 0,
                'active' => 0,
                'missing' => 0,
                'wrong_type' => 0,
                'incomplete' => 0,
                'conflict' => 0,
                'blocked_output' => 0,
                'blocked' => 0,
                'safe_auto_generate' => 0,
                'needs_manual_review' => 0,
                'google_target_pages' => 0,
                'google_ready_pages' => 0,
                'website_wide_ready_pages' => 0,
                'page_level_ready_pages' => 0,
            ),
            'queue_preview_summary' => array(),
            'policy_summary' => $this->aysa_adjustments_get_schema_policy_summary(),
            'has_data' => false,
            'needs_refresh' => true,
        );
    }

    private function aysa_adjustments_write_technical_schema_snapshot_cache($project_id, array $snapshot) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return;
        }

        set_transient(
            $this->aysa_adjustments_get_schema_cache_key($project_id, 'snapshot'),
            $snapshot,
            HOUR_IN_SECONDS
        );
        set_transient(
            $this->aysa_adjustments_get_schema_cache_key($project_id, 'summary'),
            array(
                'status' => sanitize_key((string) ($snapshot['status'] ?? 'idle')),
                'summary' => is_array($snapshot['summary'] ?? null) ? $snapshot['summary'] : array(),
                'queue_preview_summary' => is_array($snapshot['queue_preview_summary'] ?? null) ? $snapshot['queue_preview_summary'] : array(),
                'policy_summary' => is_array($snapshot['policy_summary'] ?? null) ? $snapshot['policy_summary'] : array(),
                'has_data' => !empty($snapshot['has_data']),
                'needs_refresh' => !empty($snapshot['needs_refresh']),
                'updated_at' => current_time('mysql'),
            ),
            HOUR_IN_SECONDS
        );
    }

    private function aysa_adjustments_delete_technical_schema_snapshot_cache($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return;
        }

        delete_transient($this->aysa_adjustments_get_schema_cache_key($project_id, 'snapshot'));
        delete_transient($this->aysa_adjustments_get_schema_cache_key($project_id, 'summary'));
    }

    private function aysa_adjustments_normalize_cached_technical_schema_snapshot($snapshot) {
        if (!is_array($snapshot)) {
            return null;
        }

        $coverage_rows = isset($snapshot['coverage_rows']) && is_array($snapshot['coverage_rows'])
            ? array_values(array_filter($snapshot['coverage_rows'], 'is_array'))
            : array();

        if (empty($coverage_rows)) {
            return $snapshot;
        }

        $supported_groups = $this->aysa_technical_schema_supported_entity_groups();
        $supported_coverage_rows = array_values(array_filter($coverage_rows, function ($row) use ($supported_groups) {
            $entity_group = sanitize_key((string) ($row['entity_group'] ?? ''));
            return in_array($entity_group, $supported_groups, true);
        }));

        $cached_total = intval($snapshot['summary']['total_pages'] ?? 0);
        $supported_total = count($supported_coverage_rows);
        $entity_rows = isset($snapshot['entity_rows']) && is_array($snapshot['entity_rows']) ? $snapshot['entity_rows'] : array();
        $entity_total = 0;
        foreach ($supported_groups as $entity_group) {
            $entity_total += count(isset($entity_rows[$entity_group]) && is_array($entity_rows[$entity_group]) ? $entity_rows[$entity_group] : array());
        }

        $missing_supported_group = false;
        foreach ($supported_groups as $entity_group) {
            if (!array_key_exists($entity_group, $entity_rows)) {
                $missing_supported_group = true;
                break;
            }
        }

        $has_unsupported_group = count($coverage_rows) !== $supported_total;
        $needs_rebuild = $missing_supported_group
            || $has_unsupported_group
            || $cached_total !== $supported_total
            || $entity_total !== $supported_total;

        if (!$needs_rebuild) {
            return $snapshot;
        }

        $rebuilt = $this->aysa_adjustments_build_technical_schema_snapshot_from_rows($coverage_rows);
        if (!empty($snapshot['status']) && empty($rebuilt['status'])) {
            $rebuilt['status'] = sanitize_key((string) $snapshot['status']);
        }
        if (array_key_exists('needs_refresh', $snapshot)) {
            $rebuilt['needs_refresh'] = !empty($snapshot['needs_refresh']);
        }
        if (isset($snapshot['snapshot_updated_at'])) {
            $rebuilt['snapshot_updated_at'] = sanitize_text_field((string) $snapshot['snapshot_updated_at']);
        }

        return $rebuilt;
    }

    private function aysa_adjustments_get_cached_or_idle_technical_schema_snapshot($project_id) {
        $project_id = intval($project_id);
        $state = get_option($this->aysa_technical_schema_state_key($project_id), array());
        if (!is_array($state)) {
            $state = array();
        }
        $state_status = sanitize_key((string) ($state['status'] ?? 'idle'));
        $snapshot = $this->aysa_adjustments_get_cached_technical_schema_snapshot($project_id);
        if (is_array($snapshot)) {
            $normalized_snapshot = $this->aysa_adjustments_normalize_cached_technical_schema_snapshot($snapshot);
            if (is_array($normalized_snapshot) && !empty($normalized_snapshot)) {
                if (in_array($state_status, array('queued', 'running', 'failed', 'blocked'), true)) {
                    $normalized_snapshot['status'] = $state_status;
                } elseif ($state_status === 'completed' && empty($normalized_snapshot['status'])) {
                    $normalized_snapshot['status'] = $state_status;
                }
                $normalized_snapshot['run_id'] = intval($state['run_id'] ?? ($normalized_snapshot['run_id'] ?? 0));
            }
            if (is_array($normalized_snapshot) && $normalized_snapshot !== $snapshot) {
                $this->aysa_adjustments_write_technical_schema_snapshot_cache($project_id, $normalized_snapshot);
                return $normalized_snapshot;
            }
            return $normalized_snapshot;
        }

        return array(
            'id' => 'technical_schema',
            'status' => in_array($state_status, array('queued', 'running', 'completed', 'failed', 'blocked'), true) ? $state_status : 'idle',
            'summary' => array(
                'total_pages' => 0,
                'active' => 0,
                'missing' => 0,
                'wrong_type' => 0,
                'incomplete' => 0,
                'conflict' => 0,
                'blocked_output' => 0,
                'blocked' => 0,
                'safe_auto_generate' => 0,
                'needs_manual_review' => 0,
                'google_target_pages' => 0,
                'google_ready_pages' => 0,
                'website_wide_ready_pages' => 0,
                'page_level_ready_pages' => 0,
            ),
            'count' => 0,
            'coverage_rows' => array(),
            'validation_rows' => array(),
            'generated_rows' => array(),
            'conflict_rows' => array(),
            'queue_preview_summary' => array(),
            'policy_summary' => $this->aysa_adjustments_get_schema_policy_summary(),
            'has_data' => false,
            'needs_refresh' => true,
            'run_id' => intval($state['run_id'] ?? 0),
        );
    }

    private function aysa_adjustments_get_schema_db_lookup() {
        global $wpdb;

        $table_name = esc_sql($wpdb->prefix . 'aysa_schemas');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $active_schema_urls = $wpdb->get_results(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT id, url, schema_type, schema_data, is_active, created_at, updated_at FROM {$table_name} WHERE is_active = 1",
            ARRAY_A
        );

        $schema_lookup = array();
        if (is_array($active_schema_urls)) {
            foreach ($active_schema_urls as $schema_row) {
                if (!is_array($schema_row)) {
                    continue;
                }
                $url = $this->aysa_adjustments_normalize_schema_url((string) ($schema_row['url'] ?? ''));
                if ($url === '') {
                    continue;
                }
                if (!isset($schema_lookup[$url])) {
                    $schema_lookup[$url] = array(
                        'types' => array(),
                        'rows' => array(),
                    );
                }
                $type = sanitize_key((string) ($schema_row['schema_type'] ?? ''));
                if ($type !== '' && !in_array($type, $schema_lookup[$url]['types'], true)) {
                    $schema_lookup[$url]['types'][] = $type;
                }
                $decoded = json_decode((string) ($schema_row['schema_data'] ?? ''), true);
                $schema_lookup[$url]['rows'][] = array(
                    'id' => intval($schema_row['id'] ?? 0),
                    'type' => $type,
                    'data' => is_array($decoded) ? $decoded : array(),
                    'updated_at' => sanitize_text_field((string) ($schema_row['updated_at'] ?? '')),
                );
            }
        }
        return $schema_lookup;
    }

    private function aysa_technical_schema_supported_entity_groups() {
        return array('page', 'product', 'ecommerce_category', 'article', 'business');
    }

    private function aysa_technical_schema_is_entity_group_supported($entity_group) {
        return in_array(sanitize_key((string) $entity_group), $this->aysa_technical_schema_supported_entity_groups(), true);
    }

    private function aysa_technical_schema_entity_group_label($entity_group) {
        switch (sanitize_key((string) $entity_group)) {
            case 'page':
                return 'Normal Pages';
            case 'product':
                return 'Products';
            case 'ecommerce_category':
                return 'Ecommerce Categories';
            case 'article':
                return 'Articles';
            case 'business':
                return 'Business Schema';
            default:
                return 'Other';
        }
    }

    private function aysa_technical_schema_resolve_entity_group(array $page_data) {
        $page_family = sanitize_key((string) ($page_data['page_family'] ?? ''));
        $post_type = sanitize_key((string) ($page_data['post_type'] ?? ''));
        $taxonomy = sanitize_key((string) ($page_data['taxonomy'] ?? ''));

        if ($page_family === 'homepage') {
            return 'business';
        }

        if ($page_family === 'page' && $this->aysa_adjustments_is_local_business_override_page($page_data)) {
            return 'business';
        }

        if ($page_family === 'product' || $post_type === 'product') {
            return 'product';
        }

        if ($page_family === 'article' || $post_type === 'post') {
            return 'article';
        }

        if (in_array($taxonomy, array('product_cat', 'product_tag'), true)) {
            return 'ecommerce_category';
        }

        return 'page';
    }

    private function aysa_adjustments_collect_schema_scope_pages() {
        $pages_to_check = array(
            array(
                'url' => home_url('/'),
                'title' => get_bloginfo('name'),
                'page_family' => 'homepage',
                'entity_group' => 'business',
                'object_id' => intval(get_option('page_on_front', 0)),
                'post_type' => 'page',
            ),
        );

        $post_types = get_post_types(array('public' => true), 'objects');
        foreach ($post_types as $post_type => $post_type_object) {
            if (!($post_type_object instanceof WP_Post_Type)) {
                continue;
            }
            if (in_array($post_type, array('attachment', 'revision', 'nav_menu_item', 'custom_css', 'customize_changeset'), true)) {
                continue;
            }

            $family = 'custom';
            if ($post_type === 'post') {
                $family = 'article';
            } elseif ($post_type === 'page') {
                $family = 'page';
            } elseif ($post_type === 'product') {
                $family = 'product';
            }

            $paged = 1;
            do {
                $query = new WP_Query(array(
                    'post_type' => $post_type,
                    'post_status' => 'publish',
                    'posts_per_page' => 100,
                    'paged' => $paged,
                    'orderby' => 'ID',
                    'order' => 'ASC',
                    'fields' => 'ids',
                    'no_found_rows' => false,
                    'update_post_meta_cache' => false,
                    'update_post_term_cache' => false,
                ));

                $post_ids = is_array($query->posts) ? $query->posts : array();
                foreach ($post_ids as $post_id) {
                    $page_data = array(
                        'url' => get_permalink($post_id),
                        'title' => get_the_title($post_id),
                        'page_family' => $family,
                        'object_id' => intval($post_id),
                        'post_type' => $post_type,
                    );
                    $page_data['entity_group'] = $this->aysa_technical_schema_resolve_entity_group($page_data);
                    $pages_to_check[] = array(
                        'url' => $page_data['url'],
                        'title' => $page_data['title'],
                        'page_family' => $page_data['page_family'],
                        'entity_group' => $page_data['entity_group'],
                        'object_id' => $page_data['object_id'],
                        'post_type' => $page_data['post_type'],
                    );
                }

                $max_pages = max(1, intval($query->max_num_pages));
                $paged++;
                wp_reset_postdata();
            } while (!empty($post_ids) && $paged <= $max_pages);
        }

        $taxonomies = get_taxonomies(array('public' => true), 'objects');
        foreach ($taxonomies as $taxonomy => $taxonomy_object) {
            if (!($taxonomy_object instanceof WP_Taxonomy)) {
                continue;
            }
            if (in_array($taxonomy, array('post_format', 'nav_menu', 'link_category', 'product_shipping_class'), true)) {
                continue;
            }

            $page_family = 'custom';
            if ($taxonomy === 'category') {
                $page_family = 'category';
            } elseif ($taxonomy === 'post_tag') {
                $page_family = 'tag';
            } elseif (in_array($taxonomy, array('product_cat', 'product_tag'), true)) {
                $page_family = 'category';
            }

            $offset = 0;
            $batch_size = 100;
            do {
                $terms = get_terms(array(
                    'taxonomy' => $taxonomy,
                    'hide_empty' => false,
                    'orderby' => 'term_id',
                    'order' => 'ASC',
                    'number' => $batch_size,
                    'offset' => $offset,
                ));

                if (is_wp_error($terms) || empty($terms)) {
                    break;
                }

                foreach ($terms as $term) {
                    if (!($term instanceof WP_Term)) {
                        continue;
                    }
                    $term_url = get_term_link($term);
                    if (is_wp_error($term_url)) {
                        continue;
                    }
                    $term_page_data = array(
                        'url' => $term_url,
                        'title' => $term->name,
                        'page_family' => $page_family,
                        'object_id' => intval($term->term_id),
                        'taxonomy' => $taxonomy,
                    );
                    $term_page_data['entity_group'] = $this->aysa_technical_schema_resolve_entity_group($term_page_data);
                    $pages_to_check[] = array(
                        'url' => $term_page_data['url'],
                        'title' => $term_page_data['title'],
                        'page_family' => $term_page_data['page_family'],
                        'entity_group' => $term_page_data['entity_group'],
                        'object_id' => $term_page_data['object_id'],
                        'taxonomy' => $term_page_data['taxonomy'],
                    );
                }

                $offset += $batch_size;
            } while (count($terms) === $batch_size);
        }

        $unique_pages = array();
        $seen_urls = array();
        foreach ($pages_to_check as $page_data) {
            $url = $this->aysa_adjustments_normalize_schema_url((string) ($page_data['url'] ?? ''));
            if ($url === '' || isset($seen_urls[$url])) {
                continue;
            }
            $seen_urls[$url] = true;
            $page_data['url'] = $url;
            $unique_pages[] = $page_data;
        }

        usort($unique_pages, static function ($a, $b) {
            return strcasecmp((string) ($a['title'] ?? $a['url'] ?? ''), (string) ($b['title'] ?? $b['url'] ?? ''));
        });

        return $unique_pages;
    }

    private function aysa_adjustments_flatten_schema_nodes($data, &$nodes) {
        if (empty($data)) {
            return;
        }
        if (is_array($data) && array_keys($data) === range(0, count($data) - 1)) {
            foreach ($data as $item) {
                $this->aysa_adjustments_flatten_schema_nodes($item, $nodes);
            }
            return;
        }
        if (!is_array($data)) {
            return;
        }
        if (isset($data['@graph']) && is_array($data['@graph'])) {
            foreach ($data['@graph'] as $item) {
                $this->aysa_adjustments_flatten_schema_nodes($item, $nodes);
            }
        }
        if (!isset($data['@type'])) {
            return;
        }
        $types = array_values(array_filter(array_map('strval', (array) $data['@type'])));
        if (empty($types)) {
            return;
        }
        $nodes[] = array(
            'types' => $types,
            'data' => $data,
            'source' => 'jsonld',
        );
    }

    private function aysa_adjustments_parse_schema_nodes_from_html($html) {
        $nodes = array();
        $has_debug_comments = strpos((string) $html, 'AYSA Schema Debug:') !== false;
        if ($html !== '' && preg_match_all('/<script[^>]*type=["\']application\/ld\+json["\'][^>]*>(.*?)<\/script>/is', $html, $matches)) {
            foreach ((array) ($matches[1] ?? array()) as $json_blob) {
                $decoded = json_decode(html_entity_decode(trim((string) $json_blob)), true);
                $this->aysa_adjustments_flatten_schema_nodes($decoded, $nodes);
            }
        }
        $has_breadcrumb_markup = (bool) preg_match('/schema\.org\/BreadcrumbList|aria-label=["\']breadcrumb["\']|class=["\'][^"\']*breadcrumb/i', (string) $html);
        $has_faq_markup = (bool) preg_match('/schema\.org\/FAQPage|schema\.org\/Question|schema\.org\/Answer|întrebări frecvente|frequently asked questions/i', (string) $html);
        if ($has_breadcrumb_markup && empty(array_filter($nodes, static function ($node) { return in_array('BreadcrumbList', (array) ($node['types'] ?? array()), true); }))) {
            $nodes[] = array('types' => array('BreadcrumbList'), 'data' => array(), 'source' => 'microdata');
        }
        if ($has_faq_markup && empty(array_filter($nodes, static function ($node) { return in_array('FAQPage', (array) ($node['types'] ?? array()), true); }))) {
            $nodes[] = array('types' => array('FAQPage'), 'data' => array(), 'source' => 'microdata');
        }
        return array(
            'nodes' => $nodes,
            'has_debug_comments' => $has_debug_comments,
            'has_breadcrumb_markup' => $has_breadcrumb_markup,
            'has_faq_markup' => $has_faq_markup,
        );
    }

    private function aysa_adjustments_get_schema_frontend_snapshot($url, $force_refresh = false) {
        $cache_key = $this->aysa_adjustments_get_schema_cache_key(md5((string) $url), 'frontend');
        if (!$force_refresh) {
            $cached = get_transient($cache_key);
            if (is_array($cached)) {
                return $cached;
            }
        }
        $response = wp_remote_get($url, array(
            'timeout' => 8,
            'redirection' => 3,
            'user-agent' => 'AYSA-Schema-Audit/1.0',
        ));
        $body = '';
        if (!is_wp_error($response)) {
            $body = (string) wp_remote_retrieve_body($response);
        }
        $parsed = $this->aysa_adjustments_parse_schema_nodes_from_html($body);
        $parsed['fetched_at'] = current_time('mysql');
        set_transient($cache_key, $parsed, HOUR_IN_SECONDS);
        return $parsed;
    }

    private function aysa_adjustments_has_cached_schema_frontend_snapshot($url) {
        $cache_key = $this->aysa_adjustments_get_schema_cache_key(md5((string) $url), 'frontend');
        $cached = get_transient($cache_key);
        return is_array($cached) && !empty($cached);
    }

    private function aysa_adjustments_sanitize_business_snapshot(array $raw, $source = 'local_fallback') {
        $company_name = trim((string) ($raw['company_name'] ?? $raw['name'] ?? get_bloginfo('name')));
        $phone = trim((string) ($raw['phone'] ?? $raw['telephone'] ?? ''));
        $email = trim((string) ($raw['email'] ?? ''));
        $address_line_1 = trim((string) ($raw['address_line_1'] ?? $raw['streetAddress'] ?? $raw['address'] ?? ''));
        $address_line_2 = trim((string) ($raw['address_line_2'] ?? ''));
        $city = trim((string) ($raw['city'] ?? $raw['addressLocality'] ?? ''));
        $region = trim((string) ($raw['region'] ?? $raw['addressRegion'] ?? ''));
        $postal_code = trim((string) ($raw['postal_code'] ?? $raw['postalCode'] ?? ''));
        $country = trim((string) ($raw['country'] ?? $raw['addressCountry'] ?? 'Romania'));
        $latitude = trim((string) ($raw['latitude'] ?? $raw['lat'] ?? ($raw['geo']['latitude'] ?? $raw['geo']['lat'] ?? '')));
        $longitude = trim((string) ($raw['longitude'] ?? $raw['lng'] ?? $raw['lon'] ?? ($raw['geo']['longitude'] ?? $raw['geo']['lng'] ?? $raw['geo']['lon'] ?? '')));
        $opening_hours = array_values(array_filter(array_map('sanitize_text_field', (array) ($raw['opening_hours'] ?? $raw['openingHours'] ?? array()))));
        $same_as = array_values(array_filter(array_map('esc_url_raw', (array) ($raw['same_as'] ?? $raw['sameAs'] ?? array()))));

        return array(
            'company_name' => $company_name,
            'phone' => $phone,
            'email' => $email,
            'address_line_1' => $address_line_1,
            'address_line_2' => $address_line_2,
            'city' => $city,
            'region' => $region,
            'postal_code' => $postal_code,
            'country' => $country,
            'latitude' => $latitude,
            'longitude' => $longitude,
            'opening_hours' => $opening_hours,
            'same_as' => $same_as,
            'source' => sanitize_key((string) $source),
        );
    }

    private function aysa_adjustments_get_google_maps_business_snapshot() {
        $candidate_options = array(
            'aysa_google_maps_business_snapshot',
            'aysa_google_business_profile_snapshot',
            'aysa_google_maps_nap_snapshot',
            'aysa_google_business_snapshot',
        );

        foreach ($candidate_options as $option_name) {
            $snapshot = get_option($option_name, array());
            if (!is_array($snapshot) || empty($snapshot)) {
                continue;
            }

            $normalized = $this->aysa_adjustments_sanitize_business_snapshot($snapshot, 'google_maps');
            if (
                $normalized['company_name'] !== ''
                && ($normalized['address_line_1'] !== '' || $normalized['city'] !== '')
                && ($normalized['phone'] !== '' || $normalized['email'] !== '')
            ) {
                return $normalized;
            }
        }

        return array();
    }

    private function aysa_adjustments_get_schema_business_context() {
        $general = get_option('aysa_general_info', array());
        $local = $this->aysa_adjustments_sanitize_business_snapshot(array(
            'company_name' => get_option('aysa_company_name', (string) ($general['company_name'] ?? get_bloginfo('name'))),
            'phone' => get_option('aysa_phone_number', get_option('aysa_contact_phone', (string) ($general['phone'] ?? ''))),
            'email' => get_option('aysa_email_address', get_option('admin_email', (string) ($general['email'] ?? ''))),
            'address_line_1' => get_option('aysa_address', (string) ($general['address'] ?? $general['streetAddress'] ?? '')),
            'address_line_2' => (string) ($general['address_line_2'] ?? ''),
            'city' => get_option('aysa_city', (string) ($general['city'] ?? $general['addressLocality'] ?? '')),
            'region' => (string) ($general['region'] ?? $general['addressRegion'] ?? ''),
            'postal_code' => (string) ($general['postal_code'] ?? $general['postalCode'] ?? ''),
            'country' => (string) ($general['country'] ?? $general['addressCountry'] ?? 'Romania'),
            'latitude' => (string) ($general['latitude'] ?? $general['lat'] ?? ''),
            'longitude' => (string) ($general['longitude'] ?? $general['lng'] ?? ''),
            'opening_hours' => (array) ($general['opening_hours'] ?? $general['openingHours'] ?? array()),
            'same_as' => (array) ($general['same_as'] ?? $general['sameAs'] ?? array()),
        ), 'local_fallback');
        $google_maps = $this->aysa_adjustments_get_google_maps_business_snapshot();

        if (!empty($google_maps)) {
            $google_maps['fallback'] = $local;
            return $google_maps;
        }

        return $local;
    }

    private function aysa_adjustments_has_local_business_data(array $context = array()) {
        if (empty($context)) {
            $context = $this->aysa_adjustments_get_schema_business_context();
        }

        return trim((string) ($context['company_name'] ?? '')) !== ''
            && (
                trim((string) ($context['address_line_1'] ?? '')) !== ''
                || trim((string) ($context['city'] ?? '')) !== ''
            )
            && (
                trim((string) ($context['phone'] ?? '')) !== ''
                || trim((string) ($context['email'] ?? '')) !== ''
            );
    }

    private function aysa_adjustments_is_local_business_override_page(array $page_data) {
        if ((string) ($page_data['page_family'] ?? '') !== 'page') {
            return false;
        }

        $haystack = strtolower(trim(
            (string) ($page_data['url'] ?? '') . ' ' .
            (string) ($page_data['title'] ?? '')
        ));
        if ($haystack === '') {
            return false;
        }

        foreach (array('contact', 'contacte', 'despre', 'despre-noi', 'about', 'locatie', 'locație', 'showroom', 'magazin', 'store') as $token) {
            if (strpos($haystack, $token) !== false) {
                return true;
            }
        }

        return false;
    }

    private function aysa_adjustments_get_schema_policy_summary() {
        return array(
            'homepage' => array(
                'primary' => 'WebSite',
                'business_nodes' => array('Organization', 'LocalBusiness'),
                'support_nodes' => array('FAQPage'),
            ),
            'product' => array(
                'primary' => 'Product',
                'business_nodes' => array(),
                'support_nodes' => array('BreadcrumbList', 'FAQPage'),
            ),
            'article' => array(
                'primary' => 'Article',
                'business_nodes' => array(),
                'support_nodes' => array('BreadcrumbList', 'FAQPage'),
            ),
            'page' => array(
                'primary' => 'WebPage',
                'business_nodes' => array(),
                'support_nodes' => array('BreadcrumbList', 'FAQPage'),
            ),
            'category' => array(
                'primary' => 'CollectionPage',
                'business_nodes' => array(),
                'support_nodes' => array('BreadcrumbList', 'ItemList'),
            ),
            'tag' => array(
                'primary' => 'CollectionPage',
                'business_nodes' => array(),
                'support_nodes' => array('BreadcrumbList', 'ItemList'),
            ),
            'custom' => array(
                'primary' => 'WebPage',
                'business_nodes' => array(),
                'support_nodes' => array('BreadcrumbList'),
            ),
        );
    }

    private function aysa_adjustments_get_recommended_schema_nodes($page_family, array $frontend_snapshot = array(), array $page_data = array()) {
        $policy = $this->aysa_adjustments_get_schema_policy_summary();
        $family_policy = isset($policy[$page_family]) ? $policy[$page_family] : $policy['custom'];
        $nodes = array((string) ($family_policy['primary'] ?? 'WebPage'));
        $has_local_data = $this->aysa_adjustments_has_local_business_data();

        if ($page_family === 'homepage') {
            $nodes[] = 'Organization';
            if ($has_local_data) {
                $nodes[] = 'LocalBusiness';
            }
        } elseif ($page_family === 'page' && $has_local_data && $this->aysa_adjustments_is_local_business_override_page($page_data)) {
            $nodes[] = 'LocalBusiness';
        }

        foreach ((array) ($family_policy['support_nodes'] ?? array()) as $support_node) {
            if ($support_node === 'FAQPage' && empty($frontend_snapshot['has_faq_markup'])) {
                continue;
            }
            $nodes[] = $support_node;
        }

        return array_values(array_unique($nodes));
    }

    private function aysa_adjustments_get_primary_schema_nodes($page_family) {
        $policy = $this->aysa_adjustments_get_schema_policy_summary();
        $family_policy = isset($policy[$page_family]) ? $policy[$page_family] : $policy['custom'];
        $primary = (string) ($family_policy['primary'] ?? 'WebPage');
        return $primary !== '' ? array($primary) : array('WebPage');
    }

    private function aysa_adjustments_get_primary_recommended_schema_node($page_family) {
        $primary_nodes = $this->aysa_adjustments_get_primary_schema_nodes($page_family);
        return (string) ($primary_nodes[0] ?? 'WebPage');
    }

    private function aysa_adjustments_get_google_target_node($page_family) {
        switch ((string) $page_family) {
            case 'product':
                return 'Product';
            case 'article':
                return 'Article';
            case 'page':
            case 'category':
            case 'tag':
            case 'custom':
                return 'BreadcrumbList';
            case 'homepage':
                return $this->aysa_adjustments_has_local_business_data() ? 'Organization' : '';
            default:
                return '';
        }
    }

    private function aysa_adjustments_get_google_target_label($target_node) {
        switch ((string) $target_node) {
            case 'Product':
                return 'Product';
            case 'Article':
                return 'Article';
            case 'BreadcrumbList':
                return 'Breadcrumb';
            case 'Organization':
                return 'Organization';
            default:
                return 'Not targeted by Google rich results';
        }
    }

    private function aysa_adjustments_get_website_wide_schema_nodes(array $page_data = array()) {
        $page_family = (string) ($page_data['page_family'] ?? '');
        $nodes = array();

        if ($page_family === 'homepage') {
            $nodes = array('WebSite', 'Organization');
            if ($this->aysa_adjustments_has_local_business_data()) {
                $nodes[] = 'LocalBusiness';
            }
        } elseif ($page_family === 'page' && $this->aysa_adjustments_is_local_business_override_page($page_data)) {
            $nodes = array('Organization');
            if ($this->aysa_adjustments_has_local_business_data()) {
                $nodes[] = 'LocalBusiness';
            }
        }

        return array_values(array_unique(array_filter(array_map('strval', $nodes))));
    }

    private function aysa_adjustments_get_semantic_schema_nodes($page_family, array $page_data = array()) {
        $nodes = array();
        switch ((string) $page_family) {
            case 'homepage':
                $nodes = array('WebSite', 'Organization');
                if ($this->aysa_adjustments_has_local_business_data()) {
                    $nodes[] = 'LocalBusiness';
                }
                break;
            case 'product':
                $nodes = array('Product', 'BreadcrumbList');
                break;
            case 'article':
                $nodes = array('Article', 'BreadcrumbList');
                break;
            case 'category':
            case 'tag':
                $nodes = array('CollectionPage', 'BreadcrumbList', 'ItemList');
                break;
            case 'page':
                $nodes = array('WebPage', 'BreadcrumbList');
                break;
            case 'custom':
            default:
                $nodes = array('WebPage', 'BreadcrumbList');
                break;
        }

        return array_values(array_unique(array_filter(array_map('strval', $nodes))));
    }

    private function aysa_adjustments_get_semantic_schema_label(array $nodes) {
        if (empty($nodes)) {
            return 'No semantic schema target';
        }

        return implode(' + ', array_values(array_filter(array_map('strval', $nodes))));
    }

    private function aysa_adjustments_get_google_target_state($target_node, array $frontend_snapshot) {
        $result = array(
            'supported' => $target_node !== '',
            'detected' => false,
            'ready' => false,
            'missing_fields' => array(),
            'status' => $target_node === '' ? 'not_applicable' : 'missing',
            'notes' => $target_node === ''
                ? 'This page family is not mapped to a primary Google rich result target.'
                : 'Google rich result target is missing from frontend output.',
        );

        if ($target_node === '') {
            return $result;
        }

        $all_missing = array();
        foreach ((array) ($frontend_snapshot['nodes'] ?? array()) as $node) {
            $types = array_values(array_filter(array_map('strval', (array) ($node['types'] ?? array()))));
            if (!in_array($target_node, $types, true)) {
                continue;
            }

            $result['detected'] = true;
            $missing = $this->aysa_adjustments_validate_schema_required_fields($target_node, (array) ($node['data'] ?? array()));
            if (empty($missing)) {
                $result['ready'] = true;
                $result['missing_fields'] = array();
                $result['status'] = 'ready';
                $result['notes'] = 'Frontend output satisfies the Google rich result target.';
                return $result;
            }

            $all_missing = array_merge($all_missing, array_values($missing));
        }

        $all_missing = array_values(array_unique(array_filter(array_map('strval', $all_missing))));
        $result['missing_fields'] = $all_missing;
        if ($result['detected']) {
            $result['status'] = 'incomplete';
            $result['notes'] = 'Google rich result target is present but missing required fields.';
        }

        return $result;
    }

    private function aysa_adjustments_can_generate_primary_schema($primary_type, array $page_data) {
        $primary_type = (string) $primary_type;

        switch ($primary_type) {
            case 'WebSite':
            case 'WebPage':
            case 'CollectionPage':
                return true;
            case 'Organization':
                return trim((string) get_option('aysa_company_name', get_bloginfo('name'))) !== '';
            case 'LocalBusiness':
                return $this->aysa_adjustments_has_local_business_data()
                    && (
                        (string) ($page_data['page_family'] ?? '') === 'homepage'
                        || $this->aysa_adjustments_is_local_business_override_page($page_data)
                    );
            case 'Article':
                if (intval($page_data['object_id'] ?? 0) <= 0) {
                    return false;
                }
                $post = get_post(intval($page_data['object_id']));
                return $post instanceof WP_Post
                    && trim((string) $post->post_title) !== ''
                    && intval($post->post_author) > 0
                    && trim((string) $post->post_date_gmt) !== '';
            case 'Product':
                if (intval($page_data['object_id'] ?? 0) <= 0 || !function_exists('wc_get_product')) {
                    return false;
                }
                $product = wc_get_product(intval($page_data['object_id']));
                if (!$product) {
                    return false;
                }
                return trim((string) $product->get_name()) !== ''
                    && !empty($product->get_image_id())
                    && $product->get_price() !== ''
                    && trim((string) $product->get_sku()) !== '';
        }

        return false;
    }

    private function aysa_adjustments_validate_schema_required_fields($type, array $node) {
        $missing = array();
        switch ($type) {
            case 'Organization':
            case 'LocalBusiness':
            case 'WebSite':
                if (empty($node['name'])) { $missing[] = 'name'; }
                if (empty($node['url'])) { $missing[] = 'url'; }
                if ($type === 'LocalBusiness') {
                    if (empty($node['telephone']) && empty($node['email'])) { $missing[] = 'telephone'; }
                    if (empty($node['address'])) { $missing[] = 'address'; }
                }
                break;
            case 'Product':
                if (empty($node['name'])) { $missing[] = 'name'; }
                if (empty($node['description'])) { $missing[] = 'description'; }
                if (empty($node['sku'])) { $missing[] = 'sku'; }
                if (empty($node['image'])) { $missing[] = 'image'; }
                if (empty($node['offers'])) {
                    $missing[] = 'offers';
                } else {
                    $offers = $node['offers'];
                    if (is_array($offers) && isset($offers[0]) && is_array($offers[0])) {
                        $offers = $offers[0];
                    }
                    if (empty($offers['price']) && empty($offers['lowPrice'])) { $missing[] = 'price'; }
                    if (empty($offers['priceCurrency'])) { $missing[] = 'priceCurrency'; }
                }
                break;
            case 'Article':
            case 'BlogPosting':
                if (empty($node['headline']) && empty($node['name'])) { $missing[] = 'headline'; }
                if (empty($node['author'])) { $missing[] = 'author'; }
                if (!empty($node['author']) && is_array($node['author']) && empty($node['author']['url'])) { $missing[] = 'author.url'; }
                if (empty($node['datePublished'])) { $missing[] = 'datePublished'; }
                break;
            case 'BreadcrumbList':
                if (empty($node['itemListElement']) || count((array) $node['itemListElement']) < 2) { $missing[] = 'itemListElement'; }
                break;
            case 'ItemList':
                if (empty($node['itemListElement']) || count((array) $node['itemListElement']) < 1) { $missing[] = 'itemListElement'; }
                break;
            case 'FAQPage':
                if (empty($node['mainEntity']) || count((array) $node['mainEntity']) < 1) { $missing[] = 'mainEntity'; }
                break;
        }
        return $missing;
    }

    private function aysa_adjustments_build_schema_row(array $page_data, array $db_lookup, $force_refresh = false) {
        $url = (string) ($page_data['url'] ?? '');
        $frontend = $this->aysa_adjustments_get_schema_frontend_snapshot($url, $force_refresh);
        $recommended_nodes = $this->aysa_adjustments_get_recommended_schema_nodes((string) ($page_data['page_family'] ?? 'page'), $frontend, $page_data);
        $semantic_nodes = $this->aysa_adjustments_get_semantic_schema_nodes((string) ($page_data['page_family'] ?? 'page'), $page_data);
        $primary_nodes = $this->aysa_adjustments_get_primary_schema_nodes((string) ($page_data['page_family'] ?? 'page'));
        $primary_recommended_node = $this->aysa_adjustments_get_primary_recommended_schema_node((string) ($page_data['page_family'] ?? 'page'));
        $google_target_node = $this->aysa_adjustments_get_google_target_node((string) ($page_data['page_family'] ?? 'page'));
        $google_target = $this->aysa_adjustments_get_google_target_state($google_target_node, $frontend);
        $website_wide_nodes = $this->aysa_adjustments_get_website_wide_schema_nodes($page_data);
        $business = $this->aysa_adjustments_get_schema_business_context();
        $entity_group = sanitize_key((string) ($page_data['entity_group'] ?? $this->aysa_technical_schema_resolve_entity_group($page_data)));
        $db_entry = $db_lookup[$url] ?? array('types' => array(), 'rows' => array());
        $aysa_types = array_values(array_unique(array_map('strval', (array) ($db_entry['types'] ?? array()))));
        $detected_types = array();
        $required_missing = array();
        $semantic_node_missing = array();
        $type_counts = array();
        foreach ((array) ($frontend['nodes'] ?? array()) as $node) {
            foreach ((array) ($node['types'] ?? array()) as $type) {
                $type = (string) $type;
                if ($type === '') {
                    continue;
                }
                $detected_types[] = $type;
                $type_counts[$type] = intval($type_counts[$type] ?? 0) + 1;
                $validated_missing = $this->aysa_adjustments_validate_schema_required_fields($type, (array) ($node['data'] ?? array()));
                foreach ($validated_missing as $field_name) {
                    $required_missing[] = $type . ':' . $field_name;
                }
                if (in_array($type, $semantic_nodes, true)) {
                    foreach ($validated_missing as $field_name) {
                        $semantic_node_missing[] = $type . ':' . $field_name;
                    }
                }
            }
        }
        $detected_types = array_values(array_unique($detected_types));
        $required_missing = array_values(array_unique($required_missing));
        $external_types = array_values(array_diff($detected_types, $aysa_types));
        $disable_all = (bool) get_option('aysa_disable_all_schema_output', false);
        $is_blacklisted = $this->aysa_technical_schema_is_blacklisted_url($url);
        $is_output_blocked = $disable_all || $is_blacklisted;
        $duplicate_types = array_keys(array_filter($type_counts, static function ($count) { return intval($count) > 1; }));
        $has_primary = !empty(array_intersect($detected_types, $primary_nodes));
        $has_google_target = $google_target_node !== '' && in_array($google_target_node, $detected_types, true);
        $has_external_google_target = $google_target_node !== '' && in_array($google_target_node, $external_types, true);
        $semantic_required_missing = array();
        $semantic_detected = true;
        $website_wide_required_missing = array();
        $website_wide_detected = true;
        foreach ($semantic_nodes as $semantic_type) {
            if (!in_array($semantic_type, $detected_types, true)) {
                $semantic_detected = false;
                $semantic_required_missing[] = $semantic_type;
            }
        }
        foreach ($website_wide_nodes as $website_node) {
            if (!in_array($website_node, $detected_types, true)) {
                $website_wide_detected = false;
                $website_wide_required_missing[] = $website_node;
            }
        }
        $semantic_required_missing = array_values(array_unique(array_merge($semantic_required_missing, $semantic_node_missing)));
        $semantic_ready = !empty($semantic_nodes) && $semantic_detected && empty($semantic_node_missing);
        $website_wide_ready = empty($website_wide_nodes) ? true : $website_wide_detected;
        $has_conflict = false;
        $conflict_reason = '';
        if (!empty($frontend['has_debug_comments'])) {
            $has_conflict = true;
            $conflict_reason = 'debug_output_in_frontend';
        } elseif (!empty($duplicate_types)) {
            $has_conflict = true;
            $conflict_reason = 'duplicate_schema_types';
        } elseif (!empty($aysa_types) && !empty($external_types) && !empty(array_intersect($aysa_types, $external_types))) {
            $has_conflict = true;
            $conflict_reason = 'parallel_schema_sources';
        } elseif (!empty($aysa_types) && !$disable_all && empty($detected_types)) {
            $has_conflict = true;
            $conflict_reason = 'schema_in_db_missing_in_frontend';
        }

        if ($is_output_blocked && (!empty($aysa_types) || $is_blacklisted)) {
            $status = 'blocked_output';
        } elseif ($has_conflict) {
            $status = 'conflict';
        } elseif (!$has_primary && empty($detected_types)) {
            $status = 'missing';
        } elseif (!$has_primary && !empty($detected_types)) {
            $status = 'wrong_type';
        } elseif (!empty($required_missing)) {
            $status = 'incomplete';
        } else {
            $status = 'active';
        }

        $source_summary = array();
        if (!empty($aysa_types)) {
            $source_summary[] = 'AYSA: ' . implode(', ', $aysa_types);
        }
        if (!empty($external_types)) {
            $source_summary[] = 'External: ' . implode(', ', $external_types);
        }
        if (empty($source_summary) && !empty($detected_types)) {
            $source_summary[] = 'Frontend: ' . implode(', ', $detected_types);
        }
        $notes = array();
        switch ($status) {
            case 'missing': $notes[] = 'Missing primary schema nodes in frontend output.'; break;
            case 'wrong_type': $notes[] = 'Frontend schema exists, but not the recommended primary type.'; break;
            case 'incomplete': $notes[] = 'Detected schema is missing required fields.'; break;
            case 'conflict': $notes[] = 'Multiple schema sources or duplicate schema types need review before AYSA can become the single source of truth.'; break;
            case 'blocked_output': $notes[] = 'AYSA schema output is blocked by a global disable or URL blacklist.'; break;
        }
        if (!empty($google_target['notes'])) {
            $notes[] = (string) $google_target['notes'];
        }
        $can_generate_primary = $this->aysa_adjustments_can_generate_primary_schema($primary_recommended_node, $page_data);
        $google_status = 'not_applicable';
        $google_reason = 'This page family does not target a primary Google rich result.';
        if (!empty($google_target['supported'])) {
            if ($is_output_blocked && (!empty($aysa_types) || $is_blacklisted)) {
                $google_status = 'blocked_output';
                $google_reason = 'AYSA schema output is blocked before Google-target validation can pass.';
            } elseif ($has_conflict && $has_google_target) {
                $google_status = 'needs_takeover';
                $google_reason = 'Google target is present, but duplicate or parallel schema sources require takeover.';
            } elseif ($has_conflict) {
                $google_status = 'conflict';
                $google_reason = 'Schema conflict prevents reliable Google-target validation.';
            } elseif (!empty($google_target['ready']) && !$has_conflict) {
                $google_status = 'ready';
                $google_reason = 'Google rich result target is present and validated in frontend output.';
            } elseif ((string) ($google_target['status'] ?? '') === 'incomplete') {
                if (in_array($google_target_node, array('Product', 'Article'), true)) {
                    $google_status = 'needs_fields';
                    $google_reason = 'Google target is present but missing required fields.';
                } else {
                    $google_status = !empty($aysa_types) || $has_external_google_target ? 'needs_takeover' : 'missing_target';
                    $google_reason = 'Google target is incomplete and should be rewritten to a valid frontend state.';
                }
            } elseif ($has_external_google_target || (!empty($external_types) && in_array($status, array('wrong_type', 'incomplete', 'conflict'), true))) {
                $google_status = 'needs_takeover';
                $google_reason = 'External schema is controlling the Google target and AYSA takeover is required.';
            } elseif (!$has_google_target) {
                if (in_array($google_target_node, array('Product', 'Article'), true) && !$can_generate_primary) {
                    $google_status = 'needs_fields';
                    $google_reason = 'Google target cannot be generated safely because required content fields are missing.';
                } else {
                    $google_status = 'missing_target';
                    $google_reason = 'Google rich result target is missing from frontend output.';
                }
            }
        }
        $execution_eligibility = 'manual_review';
        $execution_reason = 'Agent 8 decision required before automatic schema actions can run.';
        if ($google_status === 'ready') {
            $execution_eligibility = 'skip';
            $execution_reason = 'Page is Google-ready for its primary rich result target.';
        } elseif ($google_status === 'not_applicable') {
            $execution_eligibility = 'not_applicable';
            $execution_reason = 'No primary Google rich result target applies to this page family.';
        } elseif ($google_status === 'needs_fields') {
            $execution_eligibility = 'needs_fields';
            $execution_reason = 'Required content fields are missing for the Google rich result target.';
        } elseif ($google_status === 'needs_takeover' && (!empty($aysa_types) || !empty($has_external_google_target) || !empty($can_generate_primary) || (string) ($status ?? '') === 'conflict')) {
            $execution_eligibility = 'rewrite_takeover';
            $execution_reason = 'AYSA can take over this URL and align the Google rich result target.';
        } elseif ($google_status === 'missing_target' && empty($external_types) && $can_generate_primary) {
            $execution_eligibility = 'generate_safe';
            $execution_reason = 'Google rich result target can be generated safely.';
        } elseif ($google_status === 'blocked_output' && !empty($aysa_types)) {
            $execution_eligibility = 'rewrite_existing_aysa';
            $execution_reason = 'AYSA schema exists but output is blocked; rewrite can restore frontend visibility.';
        } elseif ($status === 'active') {
            $execution_eligibility = 'skip';
            $execution_reason = 'Page is already aligned with the recommended schema policy.';
        } elseif ($status === 'conflict' && in_array($conflict_reason, array('parallel_schema_sources', 'duplicate_schema_types', 'schema_in_db_missing_in_frontend'), true) && $can_generate_primary) {
            $execution_eligibility = 'rewrite_takeover';
            $execution_reason = 'AYSA can take over this URL and replace the external schema output.';
        } elseif ($status === 'missing' && empty($external_types) && $can_generate_primary) {
            $execution_eligibility = 'generate_safe';
            $execution_reason = 'Primary schema can be generated safely.';
        } elseif (in_array($status, array('wrong_type', 'incomplete', 'blocked_output'), true) && !$has_conflict && !empty($aysa_types)) {
            $execution_eligibility = 'rewrite_existing_aysa';
            $execution_reason = $status === 'blocked_output'
                ? 'AYSA schema exists but output is blocked; worker can restore it through rewrite.'
                : 'Existing AYSA schema can be rewritten to the recommended primary type.';
        } elseif (in_array($status, array('wrong_type', 'incomplete'), true) && empty($aysa_types) && $can_generate_primary) {
            $execution_eligibility = 'rewrite_takeover';
            $execution_reason = 'AYSA can replace the external or incomplete schema on this URL.';
        } elseif ($status === 'blocked_output' && empty($aysa_types) && $can_generate_primary) {
            $execution_eligibility = 'rewrite_takeover';
            $execution_reason = 'AYSA can generate and restore schema output on this blocked URL.';
        } elseif ($status === 'conflict') {
            $execution_eligibility = 'manual_review';
            $execution_reason = 'Conflict detected. Route this to Agent 8 before AYSA becomes source of truth.';
        } elseif ($status === 'missing' && !$can_generate_primary) {
            $execution_eligibility = 'manual_review';
            $execution_reason = 'Primary schema is not safe to auto-generate for this page. Route it to Agent 8.';
        }
        $needs_manual_review = in_array($status, array('wrong_type', 'incomplete', 'conflict', 'blocked_output'), true)
            || ($status === 'missing' && !$can_generate_primary);
        $overwrite_status = 'ready_for_overwrite';
        $overwrite_reason = 'AYSA can overwrite this URL when you want it to become source of truth.';
        if ($google_status === 'ready' && $semantic_ready && $website_wide_ready && !$has_conflict) {
            $overwrite_status = 'already_owned';
            $overwrite_reason = 'Google, semantic, and website-wide schema targets are already valid.';
        } elseif ($google_status === 'needs_fields') {
            $overwrite_status = 'needs_fields';
            $overwrite_reason = 'AYSA is missing required fields before overwrite can happen.';
        } elseif ($is_output_blocked) {
            $overwrite_status = 'blocked_output';
            $overwrite_reason = 'Schema output is blocked and must be restored before overwrite is visible.';
        } elseif ($has_conflict || $google_status === 'needs_takeover') {
            $overwrite_status = 'needs_takeover';
            $overwrite_reason = 'External schema is present; rewrite will replace it.';
        } elseif ($google_status === 'not_applicable' && $semantic_ready) {
            $overwrite_status = 'semantic_ready';
            $overwrite_reason = 'This page is semantically ready even though it has no primary Google target.';
        }
        $nap_status = $this->aysa_adjustments_has_local_business_data($business) ? 'ready' : 'needs_fields';
        $business_source = sanitize_key((string) ($business['source'] ?? 'local_fallback'));
        $location_source = $business_source;

        return array(
            'url' => esc_url_raw($url),
            'title' => sanitize_text_field((string) ($page_data['title'] ?? $url)),
            'page_family' => sanitize_key((string) ($page_data['page_family'] ?? 'page')),
            'entity_group' => $entity_group,
            'entity_group_label' => $this->aysa_technical_schema_entity_group_label($entity_group),
            'website_wide_schema' => $website_wide_nodes,
            'website_wide_schema_label' => $this->aysa_adjustments_get_semantic_schema_label($website_wide_nodes),
            'website_wide_required_missing' => array_values(array_unique($website_wide_required_missing)),
            'website_required_missing' => array_values(array_unique($website_wide_required_missing)),
            'website_wide_ready' => $website_wide_ready,
            'page_level_ready' => $semantic_ready,
            'google_target' => $google_target_node,
            'google_target_label' => $this->aysa_adjustments_get_google_target_label($google_target_node),
            'google_target_supported' => !empty($google_target['supported']),
            'google_target_detected' => !empty($google_target['detected']),
            'google_rich_result_ready' => !empty($google_target['ready']),
            'google_target_status' => sanitize_key((string) ($google_target['status'] ?? 'not_applicable')),
            'google_required_missing' => array_values((array) ($google_target['missing_fields'] ?? array())),
            'google_status' => sanitize_key($google_status),
            'google_reason' => sanitize_text_field($google_reason),
            'semantic_schema' => $semantic_nodes,
            'semantic_schema_label' => $this->aysa_adjustments_get_semantic_schema_label($semantic_nodes),
            'semantic_required_missing' => $semantic_required_missing,
            'semantic_ready' => $semantic_ready,
            'recommended_nodes' => array_values($recommended_nodes),
            'primary_recommended_node' => $primary_recommended_node,
            'detected_nodes' => array_values($detected_types),
            'status' => $status,
            'required_fields_missing' => $required_missing,
            'source_summary' => $source_summary,
            'is_aysa_present' => !empty($aysa_types),
            'is_external_schema_present' => !empty($external_types),
            'has_conflict' => $has_conflict,
            'conflict_reason' => $conflict_reason,
            'can_auto_generate' => in_array($status, array('missing', 'wrong_type'), true) && empty($external_types) && $can_generate_primary,
            'can_auto_fix' => !empty($aysa_types) && in_array($status, array('blocked_output', 'incomplete', 'wrong_type'), true) && !$has_conflict,
            'execution_eligibility' => $execution_eligibility,
            'execution_reason' => $execution_reason,
            'overwrite_status' => $overwrite_status,
            'overwrite_reason' => $overwrite_reason,
            'is_output_blocked' => $is_output_blocked,
            'blocked_output_reason' => $is_blacklisted ? 'url_blacklisted' : ($disable_all ? 'global_disable' : ''),
            'needs_manual_review' => $needs_manual_review,
            'business_source' => $business_source,
            'location_source' => $location_source,
            'nap_status' => $nap_status,
            'notes' => $notes,
            'last_checked_at' => sanitize_text_field((string) ($frontend['fetched_at'] ?? current_time('mysql'))),
            'generated_types' => $aysa_types,
        );
    }

    private function aysa_adjustments_build_technical_schema_snapshot_from_rows(array $rows) {
        $supported_groups = $this->aysa_technical_schema_supported_entity_groups();
        $unsupported_rows = array_values(array_filter($rows, function ($row) use ($supported_groups) {
            $entity_group = sanitize_key((string) ($row['entity_group'] ?? ''));
            return !in_array($entity_group, $supported_groups, true);
        }));
        $rows = array_values(array_filter($rows, function ($row) use ($supported_groups) {
            $entity_group = sanitize_key((string) ($row['entity_group'] ?? ''));
            return in_array($entity_group, $supported_groups, true);
        }));

        $summary = array(
            'total_pages' => 0,
            'active' => 0,
            'missing' => 0,
            'wrong_type' => 0,
            'incomplete' => 0,
            'conflict' => 0,
            'blocked_output' => 0,
            'blocked' => 0,
            'safe_auto_generate' => 0,
            'needs_manual_review' => 0,
            'ready_to_apply' => 0,
            'google_target_pages' => 0,
            'google_ready_pages' => 0,
            'website_wide_ready_pages' => 0,
            'page_level_ready_pages' => 0,
            'needs_takeover' => 0,
            'needs_fields' => 0,
            'not_applicable' => 0,
            'ignored_unsupported' => count($unsupported_rows),
        );
        $entity_summaries = array();
        foreach ($supported_groups as $entity_group) {
            $entity_summaries[$entity_group] = array(
                'entity_group' => $entity_group,
                'label' => $this->aysa_technical_schema_entity_group_label($entity_group),
                'total_pages' => 0,
                'google_ready_pages' => 0,
                'semantic_ready_pages' => 0,
                'website_wide_ready_pages' => 0,
                'safe_auto_generate' => 0,
                'active_generated' => 0,
                'needs_fields' => 0,
                'needs_takeover' => 0,
            );
        }
        $queue_preview = array(
            'eligible_generate' => 0,
            'eligible_rewrite_existing' => 0,
            'manual_review' => 0,
            'conflict' => 0,
            'already_aligned' => 0,
        );
        foreach ($rows as $row) {
            $summary['total_pages']++;
            if (isset($summary[$row['status']])) {
                $summary[$row['status']]++;
            }
            if (($row['status'] ?? '') === 'blocked_output') {
                $summary['blocked']++;
            }
            if (!empty($row['can_auto_generate'])) {
                $summary['safe_auto_generate']++;
            }
            $execution_eligibility = (string) ($row['execution_eligibility'] ?? 'manual_review');
            if (in_array($execution_eligibility, array('generate_safe', 'rewrite_existing_aysa', 'rewrite_takeover'), true)) {
                $summary['ready_to_apply']++;
            } elseif ($execution_eligibility === 'manual_review') {
                $summary['needs_manual_review']++;
            }
            if (!empty($row['google_target_supported'])) {
                $summary['google_target_pages']++;
            }
            if (!empty($row['google_rich_result_ready'])) {
                $summary['google_ready_pages']++;
            }
            if (!empty($row['website_wide_schema']) && !empty($row['website_wide_ready'])) {
                $summary['website_wide_ready_pages']++;
            }
            if (!empty($row['page_level_ready'])) {
                $summary['page_level_ready_pages']++;
            }
            if (isset($summary[$row['google_status'] ?? ''])) {
                $summary[$row['google_status']]++;
            }
            $entity_group = sanitize_key((string) ($row['entity_group'] ?? ''));
            if (isset($entity_summaries[$entity_group])) {
                $entity_summaries[$entity_group]['total_pages']++;
                if (!empty($row['google_rich_result_ready'])) {
                    $entity_summaries[$entity_group]['google_ready_pages']++;
                }
                if (!empty($row['semantic_ready'])) {
                    $entity_summaries[$entity_group]['semantic_ready_pages']++;
                }
                if (!empty($row['website_wide_schema']) && !empty($row['website_wide_ready'])) {
                    $entity_summaries[$entity_group]['website_wide_ready_pages']++;
                }
                if (!empty($row['can_auto_generate'])) {
                    $entity_summaries[$entity_group]['safe_auto_generate']++;
                }
                if (!empty($row['is_aysa_present'])) {
                    $entity_summaries[$entity_group]['active_generated']++;
                }
                if ((string) ($row['google_status'] ?? '') === 'needs_fields') {
                    $entity_summaries[$entity_group]['needs_fields']++;
                }
                if ((string) ($row['google_status'] ?? '') === 'needs_takeover') {
                    $entity_summaries[$entity_group]['needs_takeover']++;
                }
            }
            switch ((string) ($row['execution_eligibility'] ?? 'manual_review')) {
                case 'generate_safe':
                    $queue_preview['eligible_generate']++;
                    break;
                case 'rewrite_existing_aysa':
                    $queue_preview['eligible_rewrite_existing']++;
                    break;
                case 'rewrite_takeover':
                    $queue_preview['eligible_rewrite_existing']++;
                    break;
                case 'skip':
                    $queue_preview['already_aligned']++;
                    break;
                case 'not_applicable':
                    $queue_preview['already_aligned']++;
                    break;
                default:
                    if ((string) ($row['status'] ?? '') === 'conflict') {
                        $queue_preview['conflict']++;
                    } else {
                        $queue_preview['manual_review']++;
                    }
                    break;
            }
        }
        $snapshot = array(
            'id' => 'technical_schema',
            'status' => !empty($rows) ? 'ready' : 'idle',
            'summary' => $summary,
            'count' => count($rows),
            'coverage_rows' => array_values($rows),
            'validation_rows' => array_values($rows),
            'generated_rows' => array_values(array_filter($rows, static function ($row) {
                return (string) ($row['execution_eligibility'] ?? 'skip') !== 'skip';
            })),
            'entity_rows' => array(
                'page' => array_values(array_filter($rows, static function ($row) { return (string) ($row['entity_group'] ?? '') === 'page'; })),
                'product' => array_values(array_filter($rows, static function ($row) { return (string) ($row['entity_group'] ?? '') === 'product'; })),
                'ecommerce_category' => array_values(array_filter($rows, static function ($row) { return (string) ($row['entity_group'] ?? '') === 'ecommerce_category'; })),
                'article' => array_values(array_filter($rows, static function ($row) { return (string) ($row['entity_group'] ?? '') === 'article'; })),
                'business' => array_values(array_filter($rows, static function ($row) { return (string) ($row['entity_group'] ?? '') === 'business'; })),
            ),
            'entity_summaries' => $entity_summaries,
            'conflict_rows' => array_values(array_filter($rows, static function ($row) { return !empty($row['has_conflict']) || in_array((string) ($row['status'] ?? ''), array('blocked_output', 'wrong_type', 'incomplete'), true); })),
            'queue_preview_summary' => $queue_preview,
            'policy_summary' => $this->aysa_adjustments_get_schema_policy_summary(),
            'has_data' => !empty($rows),
            'ignored_rows' => $unsupported_rows,
        );
        return $snapshot;
    }

    private function aysa_adjustments_get_technical_schema_snapshot($project_id, $force_refresh = false) {
        $project_id = intval($project_id);
        $cache_key = $this->aysa_adjustments_get_schema_cache_key($project_id, 'snapshot');
        if (!$force_refresh) {
            $cached = get_transient($cache_key);
            if (is_array($cached)) {
                return $cached;
            }
        }
        $db_lookup = $this->aysa_adjustments_get_schema_db_lookup();
        $scope_pages = $this->aysa_adjustments_collect_schema_scope_pages();
        $rows = array();
        foreach ($scope_pages as $page_data) {
            $rows[] = $this->aysa_adjustments_build_schema_row($page_data, $db_lookup, $force_refresh);
        }
        $snapshot = $this->aysa_adjustments_build_technical_schema_snapshot_from_rows($rows);
        $this->aysa_adjustments_write_technical_schema_snapshot_cache($project_id, $snapshot);
        return $snapshot;
    }

    private function aysa_adjustments_get_technical_schema_markup_snapshot($project_id) {
        $schema_snapshot = $this->aysa_adjustments_get_cached_or_idle_technical_schema_snapshot($project_id);
        $state = get_option($this->aysa_technical_schema_state_key($project_id), array());
        if (!is_array($state)) {
            $state = array();
        }
        $state_status = sanitize_key((string) ($state['status'] ?? 'idle'));
        $cached_markup = isset($state['schema_markup_snapshot']) && is_array($state['schema_markup_snapshot'])
            ? $state['schema_markup_snapshot']
            : array();
        if ($cached_markup !== array() && empty($schema_snapshot['coverage_rows'])) {
            return $cached_markup;
        }
        $rows = array();
        foreach ((array) ($schema_snapshot['coverage_rows'] ?? array()) as $row) {
            $rows[] = array(
                'url' => (string) ($row['url'] ?? ''),
                'title' => (string) ($row['title'] ?? ''),
                'page_type' => ucfirst((string) ($row['page_family'] ?? 'page')),
                'schema_type' => !empty($row['primary_recommended_node']) ? sanitize_key((string) $row['primary_recommended_node']) : '',
                'status' => (string) ($row['status'] ?? 'missing'),
                'current_schema' => !empty($row['detected_nodes'][0]) ? sanitize_key((string) $row['detected_nodes'][0]) : '',
            );
        }
        $summary = (array) ($schema_snapshot['summary'] ?? array());
        $total_pages = intval($summary['total_pages'] ?? count($rows));
        $active_count = intval($summary['active'] ?? 0);
        $missing_count = intval($summary['missing'] ?? 0);
        $incomplete_count = intval(($summary['incomplete'] ?? 0) + ($summary['wrong_type'] ?? 0) + ($summary['conflict'] ?? 0) + ($summary['blocked'] ?? 0));
        return array(
            'id' => 'technical_schema_markup',
            'status' => !empty($rows)
                ? (in_array($state_status, array('queued', 'running', 'failed', 'blocked'), true) ? $state_status : 'ready')
                : (in_array($state_status, array('queued', 'running', 'completed', 'failed', 'blocked'), true)
                    ? $state_status
                    : ((string) ($schema_snapshot['status'] ?? 'idle'))),
            'total_pages' => $total_pages,
            'active_count' => $active_count,
            'missing_count' => $missing_count,
            'incomplete_count' => $incomplete_count,
            'coverage_percentage' => $total_pages > 0 ? intval(round(($active_count / $total_pages) * 100)) : 0,
            'overview_rows' => array(
                array('metric' => 'Total pages', 'value' => (string) $total_pages),
                array('metric' => 'Active schema', 'value' => (string) $active_count),
                array('metric' => 'Missing schema', 'value' => (string) $missing_count),
                array('metric' => 'Incomplete schema', 'value' => (string) $incomplete_count),
                array('metric' => 'Coverage', 'value' => (string) ($total_pages > 0 ? intval(round(($active_count / $total_pages) * 100)) : 0) . '%'),
            ),
            'rows' => array_values($rows),
            'count' => count($rows),
            'has_data' => !empty($rows),
            'needs_refresh' => !empty($schema_snapshot['needs_refresh']),
        );
    }

    private function aysa_technical_schema_job_key($job_id) {
        return 'aysa_technical_schema_job_' . sanitize_key((string) $job_id);
    }

    private function aysa_technical_schema_refresh_job_pointer_key($project_id) {
        return $this->aysa_adjustments_get_schema_cache_key($project_id, 'refresh_job');
    }

    private function aysa_technical_schema_read_job($job_id) {
        $job = get_transient($this->aysa_technical_schema_job_key($job_id));
        return is_array($job) ? $job : null;
    }

    private function aysa_technical_schema_write_job($job_id, array $job) {
        $job['updated_at'] = current_time('mysql');
        $job['updated_at_unix'] = time();
        set_transient($this->aysa_technical_schema_job_key($job_id), $job, 12 * HOUR_IN_SECONDS);
        return $job;
    }

    private function aysa_technical_schema_schedule_job($job_id, $delay = 1) {
        $timestamp = time() + max(1, intval($delay));
        wp_schedule_single_event($timestamp, 'aysa_process_schema_job_batch', array((string) $job_id));
    }

    private function aysa_technical_schema_get_current_refresh_job($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return null;
        }
        $job_id = get_transient($this->aysa_technical_schema_refresh_job_pointer_key($project_id));
        if (!is_string($job_id) || $job_id === '') {
            return null;
        }
        $job = $this->aysa_technical_schema_read_job($job_id);
        return is_array($job) ? $job : null;
    }

    private function aysa_technical_schema_delete_job($job_id) {
        delete_transient($this->aysa_technical_schema_job_key($job_id));
    }

    private function aysa_technical_schema_scope_lookup() {
        $lookup = array();
        foreach ((array) $this->aysa_adjustments_collect_schema_scope_pages() as $page_data) {
            if (!is_array($page_data)) {
                continue;
            }
            $url = $this->aysa_adjustments_normalize_schema_url((string) ($page_data['url'] ?? ''));
            if ($url === '') {
                continue;
            }
            $lookup[$url] = $page_data;
        }
        return $lookup;
    }

    private function aysa_technical_schema_unblacklist_url($url) {
        $url = $this->aysa_adjustments_normalize_schema_url($url);
        if ($url === '') {
            return;
        }
        $blacklist = get_option('aysa_schema_blacklist', array());
        if (!is_array($blacklist) || empty($blacklist)) {
            return;
        }
        $updated = array();
        foreach ($blacklist as $blocked_url) {
            $normalized_blocked = $this->aysa_adjustments_normalize_schema_url((string) $blocked_url);
            if ($normalized_blocked === '' || $normalized_blocked === $url) {
                continue;
            }
            $updated[] = $blocked_url;
        }
        update_option('aysa_schema_blacklist', array_values($updated), false);
    }

    private function aysa_technical_schema_blacklist_url($url) {
        $url = $this->aysa_adjustments_normalize_schema_url($url);
        if ($url === '') {
            return;
        }
        $blacklist = get_option('aysa_schema_blacklist', array());
        if (!is_array($blacklist)) {
            $blacklist = array();
        }
        if (!in_array($url, $blacklist, true)) {
            $blacklist[] = $url;
            update_option('aysa_schema_blacklist', array_values($blacklist), false);
        }
    }

    private function aysa_technical_schema_is_blacklisted_url($url) {
        $url = $this->aysa_adjustments_normalize_schema_url($url);
        if ($url === '') {
            return false;
        }

        $blacklist = get_option('aysa_schema_blacklist', array());
        if (!is_array($blacklist) || empty($blacklist)) {
            return false;
        }

        foreach ($blacklist as $blocked_url) {
            if ($this->aysa_adjustments_normalize_schema_url((string) $blocked_url) === $url) {
                return true;
            }
        }

        return false;
    }

    private function aysa_technical_schema_invalidate_caches($project_id, $url = '') {
        $project_id = intval($project_id);
        if ($project_id > 0) {
            delete_transient($this->aysa_adjustments_get_schema_cache_key($project_id, 'snapshot'));
            delete_transient($this->aysa_adjustments_get_schema_cache_key($project_id, 'summary'));
        }
        if ($url !== '') {
            delete_transient($this->aysa_adjustments_get_schema_cache_key(md5((string) $url), 'frontend'));
        }
    }

    private function aysa_technical_schema_map_type_label($schema_type) {
        $map = array(
            'website' => 'WebSite',
            'organization' => 'Organization',
            'localbusiness' => 'LocalBusiness',
            'product' => 'Product',
            'article' => 'Article',
            'webpage' => 'WebPage',
            'collectionpage' => 'CollectionPage',
            'breadcrumblist' => 'BreadcrumbList',
            'faqpage' => 'FAQPage',
        );
        $key = sanitize_key((string) $schema_type);
        return (string) ($map[$key] ?? 'WebPage');
    }

    private function aysa_technical_schema_make_item_list_entry($position, $url, $name = '', $image = '') {
        $entry = array(
            '@type' => 'ListItem',
            'position' => max(1, intval($position)),
            'url' => esc_url_raw((string) $url),
            'name' => sanitize_text_field((string) $name),
        );
        if (trim((string) $image) !== '') {
            $entry['image'] = esc_url_raw((string) $image);
        }

        return $entry;
    }

    private function aysa_technical_schema_collect_archive_item_list_elements($taxonomy, $term_id) {
        $taxonomy = sanitize_key((string) $taxonomy);
        $term_id = intval($term_id);
        if ($taxonomy === '' || $term_id <= 0 || !taxonomy_exists($taxonomy) || !class_exists('WP_Query')) {
            return array();
        }

        $query_post_types = array();
        if (
            (function_exists('taxonomy_is_product_attribute') && taxonomy_is_product_attribute($taxonomy))
            || in_array($taxonomy, array('product_cat', 'product_tag'), true)
        ) {
            $query_post_types = array('product');
        } else {
            $taxonomy_object = get_taxonomy($taxonomy);
            $object_types = is_object($taxonomy_object) ? (array) ($taxonomy_object->object_type ?? array()) : array();
            foreach ($object_types as $candidate_type) {
                $candidate_type = sanitize_key((string) $candidate_type);
                if ($candidate_type === '' || !post_type_exists($candidate_type)) {
                    continue;
                }
                $post_type_object = get_post_type_object($candidate_type);
                if (!$post_type_object || empty($post_type_object->public) || in_array($candidate_type, array('attachment', 'revision', 'nav_menu_item', 'product_variation'), true)) {
                    continue;
                }
                $query_post_types[] = $candidate_type;
            }
            if (empty($query_post_types)) {
                $query_post_types = array('post');
            }
        }

        $archive_query = new WP_Query(array(
            'post_type' => array_values(array_unique($query_post_types)),
            'post_status' => 'publish',
            'posts_per_page' => 12,
            'ignore_sticky_posts' => true,
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Reviewed WordPress query: the taxonomy selector is required by the feature and is constrained by the surrounding post type, status, limit, or administrative scope.
            'tax_query' => array(
                array(
                    'taxonomy' => $taxonomy,
                    'field' => 'term_id',
                    'terms' => array($term_id),
                ),
            ),
            'fields' => 'ids',
        ));

        $elements = array();
        if ($archive_query->have_posts()) {
            $position = 1;
            foreach ((array) $archive_query->posts as $listed_post_id) {
                $listed_post_id = intval($listed_post_id);
                if ($listed_post_id <= 0) {
                    continue;
                }
                $listed_url = get_permalink($listed_post_id);
                if (!$listed_url) {
                    continue;
                }
                $listed_name = get_the_title($listed_post_id);
                $listed_image = get_the_post_thumbnail_url($listed_post_id, 'medium');
                $elements[] = $this->aysa_technical_schema_make_item_list_entry(
                    $position,
                    $listed_url,
                    $listed_name,
                    $listed_image ? $listed_image : ''
                );
                $position++;
            }
        }
        wp_reset_postdata();

        return $elements;
    }

    private function aysa_technical_schema_build_page_context(array $page_data) {
        $url = $this->aysa_adjustments_normalize_schema_url((string) ($page_data['url'] ?? ''));
        $title = sanitize_text_field((string) ($page_data['title'] ?? $url));
        $description = '';
        $image_url = '';
        $author_name = '';
        $author_url = '';
        $published_date = '';
        $modified_date = '';
        $product_price = '';
        $product_currency = function_exists('get_woocommerce_currency') ? get_woocommerce_currency() : 'RON';
        $product_availability = 'https://schema.org/InStock';
        $product_sku = '';
        $item_list_elements = array();
        $autofill_actions = array();

        $object_id = intval($page_data['object_id'] ?? 0);
        $post_type = sanitize_key((string) ($page_data['post_type'] ?? ''));
        if ($object_id > 0 && $post_type !== '' && taxonomy_exists($post_type) === false) {
            $post = get_post($object_id);
            if ($post instanceof WP_Post) {
                if ($title === '') {
                    $title = sanitize_text_field((string) $post->post_title);
                }
                $description = sanitize_text_field(wp_strip_all_tags((string) ($post->post_excerpt ?: $post->post_content)));
                if ($description !== '') {
                    $description = wp_trim_words($description, 32, '...');
                } else {
                    $autofill_actions[] = 'Synthesized description from page title.';
                    /* translators: %s: page or site title. */
                    $description = wp_trim_words(sprintf(__('Learn more about %s on our website.', 'aysaaiseo'), $title !== '' ? $title : get_bloginfo('name')), 32, '...');
                }
                $image_id = intval(get_post_thumbnail_id($post->ID));
                if ($image_id > 0) {
                    $image_url = (string) wp_get_attachment_image_url($image_id, 'full');
                } elseif (($fallback_image = get_the_post_thumbnail_url($post->ID, 'large'))) {
                    $image_url = (string) $fallback_image;
                    $autofill_actions[] = 'Autofilled image from available attachment fallback.';
                }
                $author_name = sanitize_text_field((string) get_the_author_meta('display_name', intval($post->post_author)));
                $author_url = esc_url_raw(get_author_posts_url(intval($post->post_author)));
                if ($author_url === '') {
                    $user_url = trim((string) get_the_author_meta('user_url', intval($post->post_author)));
                    if ($user_url !== '') {
                        $author_url = esc_url_raw($user_url);
                        $autofill_actions[] = 'Autofilled author.url from WordPress author profile.';
                    }
                } elseif ((string) ($page_data['page_family'] ?? '') === 'article') {
                    $autofill_actions[] = 'Autofilled author.url from WordPress author archive.';
                }
                $published_date = mysql2date('c', (string) $post->post_date_gmt ?: (string) $post->post_date, false);
                $modified_date = mysql2date('c', (string) $post->post_modified_gmt ?: (string) $post->post_modified, false);

                if ($post_type === 'product' && function_exists('wc_get_product')) {
                    $product = wc_get_product($post->ID);
                    if ($product) {
                        $title = sanitize_text_field((string) $product->get_name());
                        $description = sanitize_text_field(wp_strip_all_tags((string) $product->get_short_description()));
                        if ($description === '') {
                            $description = sanitize_text_field(wp_strip_all_tags((string) $product->get_description()));
                            if ($description !== '') {
                                $autofill_actions[] = 'Autofilled product description from WooCommerce content.';
                            }
                        }
                        $description = wp_trim_words($description, 32, '...');
                        $image_id = intval($product->get_image_id());
                        if ($image_id > 0) {
                            $image_url = (string) wp_get_attachment_image_url($image_id, 'full');
                        } elseif (!empty($product->get_gallery_image_ids())) {
                            $gallery_ids = array_values(array_filter(array_map('intval', (array) $product->get_gallery_image_ids())));
                            if (!empty($gallery_ids[0])) {
                                $image_url = (string) wp_get_attachment_image_url($gallery_ids[0], 'full');
                                $autofill_actions[] = 'Autofilled product image from gallery.';
                            }
                        }
                        $product_price = (string) $product->get_price();
                        if ($product_price === '') {
                            $product_price = (string) ($product->get_regular_price() !== '' ? $product->get_regular_price() : $product->get_sale_price());
                            if ($product_price !== '') {
                                $autofill_actions[] = 'Autofilled product price from WooCommerce pricing metadata.';
                            }
                        }
                        $product_currency = function_exists('get_woocommerce_currency') ? get_woocommerce_currency() : $product_currency;
                        $product_availability = $product->is_in_stock() ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock';
                        $product_sku = sanitize_text_field((string) $product->get_sku());
                        if ($product_sku === '') {
                            $meta_sku = sanitize_text_field((string) get_post_meta($post->ID, '_sku', true));
                            if ($meta_sku === '') {
                                $meta_sku = sanitize_text_field((string) get_post_meta($post->ID, 'sku', true));
                            }
                            if ($meta_sku !== '') {
                                $product_sku = $meta_sku;
                                $autofill_actions[] = 'Autofilled product SKU from product meta.';
                            } elseif (trim((string) $post->post_name) !== '') {
                                $product_sku = strtoupper(sanitize_title((string) $post->post_name));
                                $autofill_actions[] = 'Autofilled product SKU from product slug.';
                            }
                        }
                    }
                }
            }
        } elseif ($object_id > 0 && !empty($page_data['taxonomy']) && taxonomy_exists((string) $page_data['taxonomy'])) {
            $term = get_term($object_id, (string) $page_data['taxonomy']);
            if ($term && !is_wp_error($term)) {
                $title = sanitize_text_field((string) $term->name);
                $description = sanitize_text_field(wp_strip_all_tags((string) $term->description));
                $description = wp_trim_words($description, 32, '...');
                if ($description === '') {
                    /* translators: %s: taxonomy/archive title. */
                    $description = sprintf(__('Browse the latest items filed under %s.', 'aysaaiseo'), $title !== '' ? $title : get_bloginfo('name'));
                    $autofill_actions[] = 'Synthesized archive description from taxonomy title.';
                }
                $item_list_elements = $this->aysa_technical_schema_collect_archive_item_list_elements((string) $page_data['taxonomy'], intval($term->term_id));
                if (!empty($item_list_elements)) {
                    $autofill_actions[] = 'Autofilled category ItemList from archive contents.';
                }
            }
        }

        if ($description === '') {
            /* translators: %s: page or site title. */
            $description = sprintf(__('Learn more about %s on our website.', 'aysaaiseo'), $title !== '' ? $title : get_bloginfo('name'));
            $autofill_actions[] = 'Synthesized fallback description from site context.';
        }

        if ($image_url === '') {
            $site_icon = (string) get_site_icon_url();
            if ($site_icon !== '') {
                $image_url = $site_icon;
                $autofill_actions[] = 'Autofilled image from site icon fallback.';
            }
        }

        return array(
            'url' => $url,
            'title' => $title !== '' ? $title : $url,
            'description' => $description,
            'image_url' => $image_url !== '' ? $image_url : (string) get_site_icon_url(),
            'author_name' => $author_name !== '' ? $author_name : 'Admin',
            'author_url' => $author_url,
            'published_date' => $published_date !== '' ? $published_date : current_time('c'),
            'modified_date' => $modified_date !== '' ? $modified_date : current_time('c'),
            'product_price' => $product_price,
            'product_currency' => $product_currency,
            'product_availability' => $product_availability,
            'product_sku' => $product_sku,
            'item_list_elements' => $item_list_elements,
            'autofill_actions' => array_values(array_unique(array_filter(array_map('sanitize_text_field', $autofill_actions)))),
        );
    }

    private function aysa_technical_schema_generate_payload($schema_type, array $page_data, array $frontend_snapshot = array()) {
        $schema_type = sanitize_key((string) $schema_type);
        $page_context = $this->aysa_technical_schema_build_page_context($page_data);
        $url = (string) ($page_context['url'] ?? '');
        if ($url === '') {
            return new WP_Error('missing_url', 'Missing URL for schema generation.');
        }

        $site_url = trailingslashit(home_url('/'));
        $company_name = trim((string) get_option('aysa_company_name', get_bloginfo('name')));
        if ($company_name === '') {
            $company_name = get_bloginfo('name');
        }
        $logo_url = (string) get_site_icon_url();
        $business = $this->aysa_adjustments_get_schema_business_context();
        $graph = array();

        $organization = array(
            '@type' => 'Organization',
            '@id' => trailingslashit($site_url) . '#organization',
            'name' => $company_name,
            'url' => trailingslashit($site_url),
        );
        if ($logo_url !== '') {
            $organization['logo'] = array(
                '@type' => 'ImageObject',
                'url' => $logo_url,
            );
        }
        if (!empty($business['same_as'])) {
            $organization['sameAs'] = array_values((array) $business['same_as']);
        }
        $graph[] = $organization;

        if (in_array($schema_type, array('website', 'webpage', 'collectionpage', 'article', 'product'), true)) {
            $graph[] = array(
                '@type' => 'WebSite',
                '@id' => trailingslashit($site_url) . '#website',
                'url' => trailingslashit($site_url),
                'name' => $company_name,
                'publisher' => array('@id' => trailingslashit($site_url) . '#organization'),
            );
        }

        if (
            ($schema_type === 'localbusiness' || (string) ($page_data['page_family'] ?? '') === 'homepage' || $this->aysa_adjustments_is_local_business_override_page($page_data))
            && $this->aysa_adjustments_has_local_business_data($business)
        ) {
            $local_business = array(
                '@type' => 'LocalBusiness',
                '@id' => trailingslashit($site_url) . '#localbusiness',
                'name' => trim((string) ($business['company_name'] ?? '')) !== '' ? sanitize_text_field((string) $business['company_name']) : $company_name,
                'url' => trailingslashit($site_url),
            );
            if (!empty($business['phone'])) {
                $local_business['telephone'] = sanitize_text_field((string) $business['phone']);
            }
            if (!empty($business['email'])) {
                $local_business['email'] = sanitize_email((string) $business['email']);
            }
            if (!empty($business['address_line_1']) || !empty($business['city'])) {
                $address = array(
                    '@type' => 'PostalAddress',
                );
                if (!empty($business['address_line_1'])) {
                    $address['streetAddress'] = sanitize_text_field((string) $business['address_line_1']);
                }
                if (!empty($business['address_line_2'])) {
                    $address['streetAddress'] = trim(((string) ($address['streetAddress'] ?? '')) . ', ' . sanitize_text_field((string) $business['address_line_2']));
                }
                if (!empty($business['city'])) {
                    $address['addressLocality'] = sanitize_text_field((string) $business['city']);
                }
                if (!empty($business['region'])) {
                    $address['addressRegion'] = sanitize_text_field((string) $business['region']);
                }
                if (!empty($business['postal_code'])) {
                    $address['postalCode'] = sanitize_text_field((string) $business['postal_code']);
                }
                if (!empty($business['country'])) {
                    $address['addressCountry'] = sanitize_text_field((string) $business['country']);
                }
                $local_business['address'] = $address;
                $local_business['location'] = $address;
            }
            if (!empty($business['latitude']) && !empty($business['longitude'])) {
                $local_business['geo'] = array(
                    '@type' => 'GeoCoordinates',
                    'latitude' => (string) $business['latitude'],
                    'longitude' => (string) $business['longitude'],
                );
            }
            if (!empty($business['opening_hours'])) {
                $local_business['openingHours'] = array_values((array) $business['opening_hours']);
            }
            if (!empty($business['same_as'])) {
                $local_business['sameAs'] = array_values((array) $business['same_as']);
            }
            $graph[] = $local_business;
        }

        if (in_array($schema_type, array('webpage', 'collectionpage', 'article', 'product'), true)) {
            $graph[] = array(
                '@type' => 'BreadcrumbList',
                '@id' => $url . '#breadcrumbs',
                'itemListElement' => array(
                    array(
                        '@type' => 'ListItem',
                        'position' => 1,
                        'name' => __('Home', 'aysaaiseo'),
                        'item' => trailingslashit($site_url),
                    ),
                    array(
                        '@type' => 'ListItem',
                        'position' => 2,
                        'name' => (string) ($page_context['title'] ?? $url),
                        'item' => $url,
                    ),
                ),
            );
        }

        switch ($schema_type) {
            case 'website':
                $graph[] = array(
                    '@type' => 'WebSite',
                    '@id' => trailingslashit($site_url) . '#website-primary',
                    'url' => trailingslashit($site_url),
                    'name' => $company_name,
                    'publisher' => array('@id' => trailingslashit($site_url) . '#organization'),
                );
                break;
            case 'organization':
                break;
            case 'localbusiness':
                break;
            case 'product':
                $product_name = trim((string) ($page_context['title'] ?? ''));
                $product_description = trim((string) ($page_context['description'] ?? ''));
                $product_sku = trim((string) ($page_context['product_sku'] ?? ''));
                $product_price = trim((string) ($page_context['product_price'] ?? ''));
                $product_image_url = trim((string) ($page_context['image_url'] ?? ''));
                $site_icon_url = (string) get_site_icon_url();
                if (
                    $product_name === ''
                    || $product_description === ''
                    || $product_sku === ''
                    || $product_price === ''
                    || $product_image_url === ''
                    || ($site_icon_url !== '' && $product_image_url === $site_icon_url)
                ) {
                    return new WP_Error('incomplete_product_payload', 'Product schema requires real name, description, SKU, price, and product image.');
                }
                $offers = array(
                    '@type' => 'Offer',
                    'url' => $url,
                    'priceCurrency' => (string) ($page_context['product_currency'] ?? 'RON'),
                    'price' => $product_price,
                    'availability' => (string) ($page_context['product_availability'] ?? 'https://schema.org/InStock'),
                    'itemCondition' => 'https://schema.org/NewCondition',
                );
                $graph[] = array(
                    '@type' => 'Product',
                    '@id' => $url . '#product',
                    'name' => $product_name,
                    'description' => $product_description,
                    'sku' => $product_sku,
                    'image' => array($product_image_url),
                    'brand' => array(
                        '@type' => 'Brand',
                        'name' => $company_name,
                    ),
                    'offers' => $offers,
                );
                break;
            case 'article':
                $graph[] = array(
                    '@type' => 'Article',
                    '@id' => $url . '#article',
                    'mainEntityOfPage' => array('@type' => 'WebPage', '@id' => $url),
                    'headline' => (string) ($page_context['title'] ?? $url),
                    'description' => (string) ($page_context['description'] ?? ''),
                    'author' => array(
                        '@type' => 'Person',
                        'name' => (string) ($page_context['author_name'] ?? 'Admin'),
                    ),
                    'datePublished' => (string) ($page_context['published_date'] ?? current_time('c')),
                    'dateModified' => (string) ($page_context['modified_date'] ?? current_time('c')),
                    'publisher' => array('@id' => trailingslashit($site_url) . '#organization'),
                    'image' => !empty($page_context['image_url']) ? array((string) $page_context['image_url']) : array(),
                );
                if (!empty($page_context['author_url'])) {
                    $graph[count($graph) - 1]['author']['url'] = esc_url_raw((string) $page_context['author_url']);
                }
                break;
            case 'collectionpage':
                $collection_page = array(
                    '@type' => 'CollectionPage',
                    '@id' => $url . '#collection',
                    'url' => $url,
                    'name' => (string) ($page_context['title'] ?? $url),
                    'description' => (string) ($page_context['description'] ?? ''),
                    'isPartOf' => array('@id' => trailingslashit($site_url) . '#website'),
                );
                $graph[] = $collection_page;
                if (!empty($page_context['item_list_elements'])) {
                    $graph[] = array(
                        '@type' => 'ItemList',
                        '@id' => $url . '#itemlist',
                        'name' => (string) ($page_context['title'] ?? $url) . ' items',
                        'itemListOrder' => 'https://schema.org/ItemListOrderAscending',
                        'numberOfItems' => count((array) $page_context['item_list_elements']),
                        'itemListElement' => array_values(array_map(static function ($item) {
                            if (!empty($item['image'])) {
                                return $item;
                            }
                            unset($item['image']);
                            return $item;
                        }, (array) $page_context['item_list_elements'])),
                    );
                }
                break;
            case 'webpage':
            default:
                $graph[] = array(
                    '@type' => 'WebPage',
                    '@id' => $url . '#webpage',
                    'url' => $url,
                    'name' => (string) ($page_context['title'] ?? $url),
                    'description' => (string) ($page_context['description'] ?? ''),
                    'isPartOf' => array('@id' => trailingslashit($site_url) . '#website'),
                );
                break;
        }

        $graph = array_values(array_filter($graph, static function ($node) {
            return is_array($node) && !empty($node['@type']);
        }));

        if (empty($graph)) {
            return new WP_Error('empty_schema', 'Schema payload could not be built.');
        }

        return array(
            '@context' => 'https://schema.org',
            '@graph' => $graph,
        );
    }

    private function aysa_technical_schema_preflight_payload($schema_type, array $item) {
        $schema_type = sanitize_key((string) $schema_type);
        $url = $this->aysa_adjustments_normalize_schema_url((string) ($item['url'] ?? ''));
        if ($url === '') {
            return new WP_Error('missing_url', 'Missing URL for schema generation.');
        }

        $frontend_snapshot = $this->aysa_adjustments_get_schema_frontend_snapshot($url, true);
        $page_context = $this->aysa_technical_schema_build_page_context($item);
        $payload = $this->aysa_technical_schema_generate_payload($schema_type, $item, $frontend_snapshot);
        if (is_wp_error($payload)) {
            return $payload;
        }

        return array(
            'payload' => $payload,
            'page_context' => $page_context,
            'frontend_snapshot' => $frontend_snapshot,
        );
    }

    private function aysa_technical_schema_classify_preflight_error(WP_Error $error, array $row_before, $schema_type) {
        $schema_type = sanitize_key((string) $schema_type);
        $code = sanitize_key((string) $error->get_error_code());
        $message = sanitize_text_field((string) $error->get_error_message());

        if (in_array($code, array('incomplete_product_payload', 'missing_url', 'empty_schema'), true)) {
            return array(
                'action_result' => ($code === 'missing_url') ? 'blocked_policy' : 'needs_fields',
                'message' => $message,
                'schema_type' => $schema_type,
            );
        }

        if (
            strpos(strtolower($message), 'missing required') !== false
            || strpos(strtolower($message), 'requires real') !== false
            || strpos(strtolower($message), 'missing') !== false
        ) {
            return array(
                'action_result' => 'needs_fields',
                'message' => $message,
                'schema_type' => $schema_type,
            );
        }

        return array(
            'action_result' => !empty($row_before['can_auto_generate']) || !empty($row_before['can_auto_fix']) ? 'needs_fields' : 'blocked_policy',
            'message' => $message,
            'schema_type' => $schema_type,
        );
    }

    private function aysa_technical_schema_resolve_scope_item(array $item, array $scope_lookup = array()) {
        $url = $this->aysa_adjustments_normalize_schema_url((string) ($item['url'] ?? ''));
        $scope_item = ($url !== '' && isset($scope_lookup[$url]) && is_array($scope_lookup[$url])) ? $scope_lookup[$url] : array();

        return array_merge($scope_item, array(
            'url' => $url,
            'title' => sanitize_text_field((string) ($item['title'] ?? ($scope_item['title'] ?? $url))),
            'page_family' => sanitize_key((string) ($item['page_family'] ?? ($scope_item['page_family'] ?? 'page'))),
            'entity_group' => sanitize_key((string) ($item['entity_group'] ?? ($scope_item['entity_group'] ?? $this->aysa_technical_schema_resolve_entity_group($scope_item)))),
            'schema_type' => sanitize_key((string) ($item['schema_type'] ?? ($item['primary_recommended_node'] ?? ($scope_item['schema_type'] ?? '')))),
            'primary_recommended_node' => sanitize_text_field((string) ($item['primary_recommended_node'] ?? '')),
        ));
    }

    private function aysa_technical_schema_normalize_mode($mode) {
        $mode = sanitize_key((string) $mode);
        if (preg_match('/^entity_(page|product|ecommerce_category|article|business)_(generate|rewrite|validate|test_selected)$/', $mode, $matches)) {
            switch ((string) $matches[2]) {
                case 'generate':
                    return 'generate_safe';
                case 'rewrite':
                    return 'rewrite_recommended';
                case 'validate':
                    return 'validate';
                case 'test_selected':
                    return 'clean_google_rewrite_selected';
            }
        }

        return $mode;
    }

    private function aysa_technical_schema_get_single_row(array $item, $force_refresh = true) {
        $scope_item = $this->aysa_technical_schema_resolve_scope_item($item, $this->aysa_technical_schema_scope_lookup());
        $url = (string) ($scope_item['url'] ?? '');
        if ($url === '') {
            return array();
        }

        return $this->aysa_adjustments_build_schema_row(
            $scope_item,
            $this->aysa_adjustments_get_schema_db_lookup(),
            $force_refresh
        );
    }

    private function aysa_technical_schema_execute_disable($project_id, $url, $schema_type = '') {
        $url = $this->aysa_adjustments_normalize_schema_url($url);
        if ($url === '') {
            return new WP_Error('missing_url', 'Missing URL');
        }

        if (class_exists('AysaSchemaManager')) {
            AysaSchemaManager::remove_schema($url, $schema_type !== '' ? $schema_type : null);
        }
        $this->aysa_technical_schema_blacklist_url($url);
        $this->aysa_technical_schema_invalidate_caches($project_id, $url);

        return array(
            'url' => $url,
            'schema_type' => sanitize_key((string) $schema_type),
        );
    }

    private function aysa_technical_schema_plan_action($mode, array $row_before, array $item) {
        $mode = $this->aysa_technical_schema_normalize_mode($mode);
        $clean_mode = in_array($mode, array('clean_google_rewrite_all', 'clean_google_rewrite_selected'), true);
        $status_before = sanitize_key((string) ($row_before['status'] ?? 'missing'));
        $google_status = sanitize_key((string) ($row_before['google_status'] ?? 'not_applicable'));
        $google_target = sanitize_text_field((string) ($row_before['google_target'] ?? ''));
        $schema_type = sanitize_key((string) ($item['schema_type'] ?? ($row_before['primary_recommended_node'] ?? '')));
        if ($schema_type === '') {
            $schema_type = sanitize_key((string) ($row_before['primary_recommended_node'] ?? 'webpage'));
        }
        if (in_array($google_target, array('Product', 'Article'), true)) {
            $schema_type = sanitize_key($google_target);
        }

        if ($clean_mode) {
            if ($status_before === 'missing' && empty($row_before['google_target_supported']) && empty($row_before['semantic_schema'])) {
                return array('action' => 'not_applicable', 'schema_type' => $schema_type, 'reason' => 'This URL has no Google or semantic schema target for clean rewrite.');
            }
            if (!empty($row_before['has_conflict']) && empty($row_before['can_auto_generate']) && empty($row_before['can_auto_fix']) && empty($row_before['is_aysa_present']) && empty($row_before['is_external_schema_present'])) {
                return array('action' => 'conflict', 'schema_type' => $schema_type, 'reason' => 'Schema conflict prevents clean rewrite on this URL.');
            }
            if ($google_status === 'blocked_output' && empty($row_before['is_aysa_present']) && empty($row_before['can_auto_generate']) && empty($row_before['can_auto_fix'])) {
                return array('action' => 'blocked_output', 'schema_type' => $schema_type, 'reason' => 'AYSA schema output is blocked and no valid payload can be restored automatically.');
            }
            if (
                $google_status === 'not_applicable'
                && !empty($row_before['semantic_schema'])
                && !empty($row_before['can_auto_generate'])
                && empty($row_before['is_external_schema_present'])
            ) {
                return array('action' => 'generate_safe', 'schema_type' => $schema_type, 'reason' => 'AYSA will generate the semantic schema stack on this URL.');
            }
            if (
                !empty($row_before['google_target_supported'])
                || !empty($row_before['semantic_schema'])
                || !empty($row_before['is_external_schema_present'])
                || !empty($row_before['is_aysa_present'])
                || !empty($row_before['can_auto_generate'])
                || !empty($row_before['can_auto_fix'])
            ) {
                return array('action' => 'rewrite_recommended', 'schema_type' => $schema_type, 'reason' => 'AYSA will rewrite this URL and become the schema source of truth.');
            }
            return array('action' => 'blocked_policy', 'schema_type' => $schema_type, 'reason' => 'Clean rewrite could not build a valid payload for this URL.');
        }

        if (in_array($mode, array('full_sitewide_orchestrator', 'force_rewrite_all', 'test_selected_urls'), true)) {
            if ($google_status === 'not_applicable') {
                if (!empty($row_before['semantic_schema']) && !empty($row_before['can_auto_generate'])) {
                    return array('action' => 'generate_safe', 'schema_type' => $schema_type, 'reason' => 'No primary Google target applies, but AYSA can generate the semantic schema stack.');
                }
                if (!empty($row_before['semantic_schema']) && ((string) ($row_before['overwrite_status'] ?? '') === 'needs_takeover' || !empty($row_before['is_external_schema_present']) || !empty($row_before['can_auto_fix']))) {
                    return array('action' => 'rewrite_recommended', 'schema_type' => $schema_type, 'reason' => 'AYSA will rewrite this URL to own the semantic schema stack.');
                }
                return array('action' => 'not_applicable', 'schema_type' => $schema_type, 'reason' => 'This page family has no primary Google rich result target.');
            }
            if ($google_status === 'ready') {
                if ($mode === 'force_rewrite_all' && ((string) ($row_before['overwrite_status'] ?? '') === 'needs_takeover' || !$row_before['semantic_ready'])) {
                    return array('action' => 'rewrite_recommended', 'schema_type' => $schema_type, 'reason' => 'Google target is valid, but AYSA will still rewrite to own the semantic schema stack.');
                }
                return array('action' => 'skip', 'schema_type' => $schema_type, 'reason' => 'Google rich result target is already valid in frontend output.');
            }
            if ($google_status === 'needs_fields') {
                return array(
                    'action' => 'needs_fields',
                    'schema_type' => $schema_type,
                    'reason' => 'Google target ' . $google_target . ' missing required fields: ' . implode(', ', array_values((array) ($row_before['google_required_missing'] ?? array()))),
                );
            }
            if ($google_status === 'blocked_output') {
                if (!empty($row_before['is_aysa_present'])) {
                    return array('action' => 'rewrite_recommended', 'schema_type' => $schema_type, 'reason' => 'AYSA output is blocked; rewrite will restore frontend schema.');
                }
                return array('action' => 'blocked_output', 'schema_type' => $schema_type, 'reason' => 'AYSA schema output is blocked and there is no existing AYSA schema to restore automatically.');
            }
            if ($google_status === 'needs_takeover') {
                if (!empty($row_before['is_aysa_present']) || !empty($row_before['can_auto_generate']) || !empty($row_before['can_auto_fix']) || (string) ($row_before['execution_eligibility'] ?? '') === 'rewrite_takeover' || in_array($mode, array('force_rewrite_all', 'test_selected_urls'), true)) {
                    return array('action' => 'rewrite_recommended', 'schema_type' => $schema_type, 'reason' => 'External ' . $google_target . ' schema detected; AYSA takeover required.');
                }
                return array('action' => 'needs_takeover', 'schema_type' => $schema_type, 'reason' => 'Google target takeover is required before AYSA can become source of truth.');
            }
            if ($google_status === 'missing_target') {
                if (!empty($row_before['can_auto_generate']) || in_array($mode, array('force_rewrite_all', 'test_selected_urls'), true)) {
                    return array('action' => 'generate_safe', 'schema_type' => $schema_type, 'reason' => 'Google target ' . $google_target . ' can be generated safely.');
                }
                return array('action' => 'blocked_policy', 'schema_type' => $schema_type, 'reason' => 'Google rich result target is missing and cannot be generated safely.');
            }
            if ($google_status === 'conflict') {
                return array('action' => 'conflict', 'schema_type' => $schema_type, 'reason' => 'Schema conflict prevents reliable Google-target execution.');
            }
            if ($status_before === 'conflict') {
                if (in_array((string) ($row_before['conflict_reason'] ?? ''), array('parallel_schema_sources', 'duplicate_schema_types', 'schema_in_db_missing_in_frontend'), true)
                    && (!empty($row_before['can_auto_generate']) || !empty($row_before['is_aysa_present']) || !empty($row_before['can_auto_fix']) || (string) ($row_before['execution_eligibility'] ?? '') === 'rewrite_takeover')) {
                    return array('action' => 'rewrite_recommended', 'schema_type' => $schema_type, 'reason' => 'AYSA will take over this URL and replace the external schema output.');
                }
                return array('action' => 'conflict', 'schema_type' => $schema_type, 'reason' => 'Agent 8 decision required before AYSA becomes source of truth.');
            }
            if ($status_before === 'missing') {
                if (!empty($row_before['can_auto_generate']) || in_array($mode, array('force_rewrite_all', 'test_selected_urls'), true)) {
                    return array('action' => 'generate_safe', 'schema_type' => $schema_type);
                }
                return array('action' => 'blocked_policy', 'schema_type' => $schema_type, 'reason' => 'Primary schema is not safe to auto-generate for this page.');
            }
            if (in_array($status_before, array('wrong_type', 'incomplete', 'blocked_output'), true)) {
                if (!empty($row_before['has_conflict'])) {
                    return array('action' => 'conflict', 'schema_type' => $schema_type, 'reason' => 'Schema conflict blocks automatic repair.');
                }
                if (!empty($row_before['is_aysa_present']) || !empty($row_before['can_auto_generate']) || !empty($row_before['can_auto_fix']) || (string) ($row_before['execution_eligibility'] ?? '') === 'rewrite_takeover') {
                    return array('action' => 'rewrite_recommended', 'schema_type' => $schema_type);
                }
                if ($status_before === 'blocked_output') {
                    return array('action' => 'blocked_output', 'schema_type' => $schema_type, 'reason' => 'AYSA schema output is blocked and there is no existing AYSA schema to restore automatically.');
                }
                return array('action' => 'blocked_policy', 'schema_type' => $schema_type, 'reason' => 'Page is not eligible for automatic rewrite.');
            }
            return array('action' => 'skip', 'schema_type' => $schema_type, 'reason' => 'Page is already aligned with the recommended schema policy.');
        }

        if ($mode === 'generate_safe') {
            if ($google_status === 'needs_fields') {
                return array('action' => 'needs_fields', 'schema_type' => $schema_type, 'reason' => 'Selected row is missing required Google-target fields.');
            }
            if (!empty($row_before['can_auto_generate'])) {
                return array('action' => 'generate_safe', 'schema_type' => $schema_type);
            }
            return array('action' => 'blocked_policy', 'schema_type' => $schema_type, 'reason' => 'Selected row is not safe to auto-generate.');
        }

        if ($mode === 'rewrite_recommended') {
            if ($google_status === 'needs_fields') {
                return array('action' => 'needs_fields', 'schema_type' => $schema_type, 'reason' => 'Selected row is missing required Google-target fields.');
            }
            if ($google_status === 'not_applicable') {
                if (!empty($row_before['semantic_schema']) && (!empty($row_before['is_external_schema_present']) || !empty($row_before['can_auto_generate']) || !empty($row_before['can_auto_fix']) || !empty($row_before['is_aysa_present']))) {
                    return array('action' => 'rewrite_recommended', 'schema_type' => $schema_type, 'reason' => 'Selected row will be rewritten to the semantic AYSA schema stack.');
                }
                return array('action' => 'not_applicable', 'schema_type' => $schema_type, 'reason' => 'Selected row has no primary Google rich result target.');
            }
            if (!empty($row_before['has_conflict'])) {
                if (in_array((string) ($row_before['conflict_reason'] ?? ''), array('parallel_schema_sources', 'duplicate_schema_types', 'schema_in_db_missing_in_frontend'), true)
                    && (!empty($row_before['can_auto_generate']) || !empty($row_before['is_aysa_present']) || !empty($row_before['can_auto_fix']) || (string) ($row_before['execution_eligibility'] ?? '') === 'rewrite_takeover')) {
                    return array('action' => 'rewrite_recommended', 'schema_type' => $schema_type, 'reason' => 'AYSA will take over this URL and replace the external schema output.');
                }
                return array('action' => 'conflict', 'schema_type' => $schema_type, 'reason' => 'Conflict detected. AYSA will not rewrite automatically.');
            }
            if (!empty($row_before['is_aysa_present']) || !empty($row_before['can_auto_fix']) || !empty($row_before['can_auto_generate']) || (string) ($row_before['execution_eligibility'] ?? '') === 'rewrite_takeover') {
                return array('action' => 'rewrite_recommended', 'schema_type' => $schema_type);
            }
            return array('action' => 'blocked_policy', 'schema_type' => $schema_type, 'reason' => 'Selected row cannot be rewritten automatically.');
        }

        if ($mode === 'disable_selected') {
            return array('action' => 'disable_selected', 'schema_type' => $schema_type);
        }

        if (in_array($mode, array('validate', 'revalidate', 'scan'), true)) {
            return array('action' => 'validate', 'schema_type' => $schema_type);
        }

        return array('action' => 'validate', 'schema_type' => $schema_type);
    }

    private function aysa_technical_schema_run_action($project_id, $action, array $item) {
        $action = sanitize_key((string) $action);
        $url = $this->aysa_adjustments_normalize_schema_url((string) ($item['url'] ?? ''));
        $schema_type = sanitize_key((string) ($item['schema_type'] ?? ($item['primary_recommended_node'] ?? 'webpage')));

        switch ($action) {
            case 'generate_safe':
            case 'rewrite_recommended':
                return $this->aysa_technical_schema_rewrite_single_item($project_id, array_merge($item, array(
                    'url' => $url,
                    'schema_type' => $schema_type,
                )));
            case 'disable_selected':
                return $this->aysa_technical_schema_execute_disable($project_id, $url, $schema_type);
            case 'validate':
                return array(
                    'url' => $url,
                    'schema_type' => $schema_type,
                );
            default:
                return new WP_Error('unsupported_schema_action', 'Unsupported schema action: ' . $action);
        }
    }

    private function aysa_technical_schema_rewrite_single_item($project_id, array $item) {
        $url = $this->aysa_adjustments_normalize_schema_url((string) ($item['url'] ?? ''));
        if ($url === '') {
            return new WP_Error('missing_url', 'Missing URL');
        }

        $schema_type = sanitize_key((string) ($item['schema_type'] ?? $item['primary_recommended_node'] ?? 'webpage'));
        if ($schema_type === '') {
            $schema_type = 'webpage';
        }

        $preflight = $this->aysa_technical_schema_preflight_payload($schema_type, $item);
        if (is_wp_error($preflight)) {
            return $preflight;
        }
        $payload = (array) ($preflight['payload'] ?? array());

        if (class_exists('AysaSchemaManager')) {
            AysaSchemaManager::remove_schema($url, null);
        }
        $this->aysa_technical_schema_unblacklist_url($url);

        $inserted = class_exists('AysaSchemaManager')
            ? AysaSchemaManager::insert_schema($url, $schema_type, wp_json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), true)
            : false;

        if (!$inserted) {
            return new WP_Error('schema_insert_failed', 'Failed to insert rewritten schema.');
        }

        $this->aysa_technical_schema_invalidate_caches($project_id, $url);

        return array(
            'url' => $url,
            'schema_type' => $schema_type,
            'schema_id' => intval($inserted),
        );
    }

    private function aysa_technical_schema_get_job_response(array $job) {
        return array(
            'job_id' => sanitize_text_field((string) ($job['job_id'] ?? '')),
            'job_kind' => sanitize_key((string) ($job['job_kind'] ?? 'worker')),
            'mode' => sanitize_key((string) ($job['mode'] ?? 'validate')),
            'entity_group' => sanitize_key((string) ($job['entity_group'] ?? '')),
            'scope' => sanitize_key((string) ($job['scope'] ?? 'selection')),
            'status' => sanitize_key((string) ($job['status'] ?? 'queued')),
            'total' => intval($job['total'] ?? 0),
            'processed' => intval($job['processed'] ?? 0),
            'successful' => intval($job['successful'] ?? 0),
            'failed' => intval($job['failed'] ?? 0),
            'skipped' => intval($job['skipped'] ?? 0),
            'generated' => intval($job['generated'] ?? 0),
            'rewritten' => intval($job['rewritten'] ?? 0),
            'overwritten' => intval($job['overwritten'] ?? ($job['rewritten'] ?? 0)),
            'autofilled' => intval($job['autofilled'] ?? 0),
            'validated' => intval($job['validated'] ?? 0),
            'failed_payload' => intval($job['failed_payload'] ?? ($job['failed'] ?? 0)),
            'needs_takeover' => intval($job['needs_takeover'] ?? 0),
            'needs_fields' => intval($job['needs_fields'] ?? 0),
            'skipped_google_ready' => intval($job['skipped_google_ready'] ?? 0),
            'not_applicable' => intval($job['not_applicable'] ?? 0),
            'failed_post_validation' => intval($job['failed_post_validation'] ?? 0),
            'google_ready' => intval($job['google_ready'] ?? 0),
            'semantic_ready' => intval($job['semantic_ready'] ?? 0),
            'website_wide_ready_after' => intval($job['website_wide_ready_after'] ?? 0),
            'page_level_ready_after' => intval($job['page_level_ready_after'] ?? 0),
            'blocked_policy' => intval($job['blocked_policy'] ?? 0),
            'blocked_output' => intval($job['blocked_output'] ?? 0),
            'blocked' => intval($job['blocked'] ?? 0),
            'conflict' => intval($job['conflict'] ?? 0),
            'cached' => intval($job['cached'] ?? 0),
            'refetched' => intval($job['refetched'] ?? 0),
            'project_id' => intval($job['project_id'] ?? 0),
            'updated_at' => sanitize_text_field((string) ($job['updated_at'] ?? '')),
            'snapshot_updated_at' => sanitize_text_field((string) ($job['snapshot_updated_at'] ?? '')),
            'failed_urls' => array_values(array_slice((array) ($job['failed_urls'] ?? array()), 0, 10)),
            'results' => array_values(array_slice((array) ($job['results'] ?? array()), 0, 25)),
            'last_error' => sanitize_text_field((string) ($job['last_error'] ?? '')),
        );
    }

    private function aysa_technical_schema_post_validation_passes($mode, array $row_after) {
        $mode = sanitize_key((string) $mode);
        $clean_mode = in_array($mode, array('clean_google_rewrite_all', 'clean_google_rewrite_selected'), true);
        if (!$clean_mode) {
            $google_required = !empty($row_after['google_target_supported']);
            $google_ok = !$google_required || (string) ($row_after['google_status'] ?? 'missing_target') === 'ready';
            $semantic_ok = empty($row_after['semantic_schema']) || !empty($row_after['semantic_ready']);
            return $google_ok && $semantic_ok;
        }

        $page_family = sanitize_key((string) ($row_after['page_family'] ?? 'page'));
        $google_required = !empty($row_after['google_target_supported']);
        $semantic_required = !empty($row_after['semantic_schema']);
        $google_ok = true;
        $semantic_ok = true;

        if ($page_family === 'homepage') {
            $semantic_ok = !$semantic_required || !empty($row_after['semantic_ready']);
            $website_wide_ok = !empty($row_after['website_wide_ready']);
            if ((string) ($row_after['nap_status'] ?? '') === 'needs_fields') {
                return $semantic_ok && $website_wide_ok;
            }
            return $semantic_ok && $website_wide_ok;
        }

        if (in_array($page_family, array('category', 'tag'), true)) {
            $google_ok = !$google_required || (string) ($row_after['google_status'] ?? 'missing_target') === 'ready';
            $semantic_ok = !$semantic_required || !empty($row_after['semantic_ready']);
            return $google_ok && $semantic_ok;
        }

        if (in_array($page_family, array('product', 'article'), true)) {
            return !$google_required || (string) ($row_after['google_status'] ?? 'missing_target') === 'ready';
        }

        if ($google_required) {
            $google_ok = (string) ($row_after['google_status'] ?? 'missing_target') === 'ready';
        }
        if ($semantic_required) {
            $semantic_ok = !empty($row_after['semantic_ready']);
        }
        $website_ok = empty($row_after['website_wide_schema']) || !empty($row_after['website_wide_ready']);

        return $google_ok && $semantic_ok && $website_ok;
    }

    private function aysa_technical_schema_process_refresh_job(array $job, $chunk_size = 12) {
        $items = isset($job['items']) && is_array($job['items']) ? array_values($job['items']) : array();
        $rows = isset($job['refresh_rows']) && is_array($job['refresh_rows']) ? $job['refresh_rows'] : array();
        $db_lookup = $this->aysa_adjustments_get_schema_db_lookup();
        $processed_in_chunk = 0;

        while (!empty($items) && $processed_in_chunk < max(1, intval($chunk_size))) {
            $item = array_shift($items);
            $item = is_array($item) ? $item : array();
            $url = $this->aysa_adjustments_normalize_schema_url((string) ($item['url'] ?? ''));
            if ($url === '') {
                $job['processed'] = intval($job['processed'] ?? 0) + 1;
                $job['failed'] = intval($job['failed'] ?? 0) + 1;
                $job['failed_urls'][] = '';
                $processed_in_chunk++;
                continue;
            }

            if ($this->aysa_adjustments_has_cached_schema_frontend_snapshot($url)) {
                $job['cached'] = intval($job['cached'] ?? 0) + 1;
            }

            try {
                $row = $this->aysa_adjustments_build_schema_row($item, $db_lookup, true);
                $rows[$url] = $row;
                $this->aysa_schema_inventory_upsert_row(intval($job['project_id'] ?? 0), $item, $row);
                $job['refetched'] = intval($job['refetched'] ?? 0) + 1;
            } catch (Throwable $e) {
                $job['failed'] = intval($job['failed'] ?? 0) + 1;
                $job['last_error'] = $e->getMessage();
                $job['failed_urls'][] = sanitize_text_field($url);
                $job['results'][] = array(
                    'url' => esc_url_raw($url),
                    'page_family' => sanitize_key((string) ($item['page_family'] ?? 'page')),
                    'action_result' => 'failed',
                    'message' => sanitize_text_field($e->getMessage()),
                );
            }

            $job['processed'] = intval($job['processed'] ?? 0) + 1;
            $processed_in_chunk++;
        }

        $job['items'] = array_values($items);
        $job['refresh_rows'] = $rows;

        if (empty($job['items'])) {
            $this->aysa_schema_inventory_finalize_reconcile(intval($job['project_id'] ?? 0), array_keys($rows));
            $job['status'] = intval($job['failed'] ?? 0) > 0 && empty($rows) ? 'failed' : 'completed';
            $job['snapshot_updated_at'] = current_time('mysql');
            delete_transient($this->aysa_technical_schema_refresh_job_pointer_key(intval($job['project_id'] ?? 0)));
            // Build the client projection only after the active pointer is
            // cleared. Otherwise the completed snapshot reads its own stale
            // running job and remains labelled "reconciling" indefinitely.
            $this->aysa_schema_inventory_update_client_context(intval($job['project_id'] ?? 0));
        } else {
            $job['status'] = 'running';
        }

        return $job;
    }

    private function aysa_technical_schema_process_job($job_id, $chunk_size = 12) {
        $job = $this->aysa_technical_schema_read_job($job_id);
        if (!$job) {
            return null;
        }
        $project_id = $this->aysa_resolve_installed_project_id(intval($job['project_id'] ?? 0));
        if ($project_id <= 0) {
            return null;
        }
        $job['project_id'] = $project_id;

        $status = (string) ($job['status'] ?? 'queued');
        if (in_array($status, array('completed', 'failed', 'cancelled'), true)) {
            return $job;
        }

        $lock_until = intval($job['lock_until'] ?? 0);
        if ($lock_until > time()) {
            return $job;
        }

        $job['status'] = 'running';
        $job['lock_until'] = time() + 20;
        $job = $this->aysa_technical_schema_write_job($job_id, $job);

        if ((string) ($job['job_kind'] ?? 'worker') === 'snapshot_refresh') {
            $job = $this->aysa_technical_schema_process_refresh_job($job, $chunk_size);
            $job['lock_until'] = 0;
            $job = $this->aysa_technical_schema_write_job($job_id, $job);
            if ((string) ($job['status'] ?? '') === 'running') {
                $this->aysa_technical_schema_schedule_job($job_id, 1);
            }
            return $job;
        }

        $items = isset($job['items']) && is_array($job['items']) ? array_values($job['items']) : array();
        $processed_in_chunk = 0;

        while (!empty($items) && $processed_in_chunk < max(1, intval($chunk_size))) {
            $item = array_shift($items);
            $item = is_array($item) ? $item : array();
            $row_before = $this->aysa_technical_schema_get_single_row($item, true);
            $action_plan = $this->aysa_technical_schema_plan_action((string) ($job['mode'] ?? 'validate'), $row_before, $item);

            $result_entry = array(
                'url' => esc_url_raw((string) ($item['url'] ?? ($row_before['url'] ?? ''))),
                'title' => sanitize_text_field((string) ($item['title'] ?? ($row_before['title'] ?? ''))),
                'page_family' => sanitize_key((string) ($item['page_family'] ?? ($row_before['page_family'] ?? 'page'))),
                'entity_group' => sanitize_key((string) ($item['entity_group'] ?? ($row_before['entity_group'] ?? ''))),
                'schema_type' => sanitize_key((string) ($action_plan['schema_type'] ?? ($row_before['primary_recommended_node'] ?? 'webpage'))),
                'google_target' => sanitize_text_field((string) ($row_before['google_target_label'] ?? ($row_before['google_target'] ?? ''))),
                'semantic_schema' => sanitize_text_field((string) ($row_before['semantic_schema_label'] ?? '')),
                'website_wide_schema' => sanitize_text_field((string) ($row_before['website_wide_schema_label'] ?? '')),
                'google_status' => sanitize_key((string) ($row_before['google_status'] ?? 'not_applicable')),
                'semantic_before' => !empty($row_before['semantic_ready']) ? 'ready' : 'needs_update',
                'website_wide_before' => !empty($row_before['website_wide_ready']) ? 'ready' : 'needs_update',
                'primary_recommended_node' => sanitize_text_field((string) ($row_before['primary_recommended_node'] ?? '')),
                'status_before' => sanitize_key((string) ($row_before['status'] ?? 'missing')),
                'eligibility' => sanitize_key((string) ($row_before['execution_eligibility'] ?? 'manual_review')),
                'overwrite_status' => sanitize_key((string) ($row_before['overwrite_status'] ?? 'ready_for_overwrite')),
                'action_planned' => sanitize_key((string) ($action_plan['action'] ?? 'validate')),
                'action_result' => 'pending',
                'google_before' => sanitize_key((string) ($row_before['google_status'] ?? 'not_applicable')),
                'autofill_actions' => array(),
                'validation_after' => array(),
                'message' => sanitize_text_field((string) ($action_plan['reason'] ?? '')),
            );

            $job['processed'] = intval($job['processed'] ?? 0) + 1;

            if (($action_plan['action'] ?? '') === 'conflict') {
                $job['conflict'] = intval($job['conflict'] ?? 0) + 1;
                $job['skipped'] = intval($job['skipped'] ?? 0) + 1;
                $result_entry['action_result'] = 'conflict';
                $this->aysa_schema_inventory_upsert_row(intval($job['project_id'] ?? 0), $item, $row_before);
            } elseif (($action_plan['action'] ?? '') === 'skip') {
                $job['skipped'] = intval($job['skipped'] ?? 0) + 1;
                $job['skipped_google_ready'] = intval($job['skipped_google_ready'] ?? 0) + 1;
                $result_entry['action_result'] = 'skipped';
                $this->aysa_schema_inventory_upsert_row(intval($job['project_id'] ?? 0), $item, $row_before);
            } elseif (($action_plan['action'] ?? '') === 'not_applicable') {
                $job['skipped'] = intval($job['skipped'] ?? 0) + 1;
                $job['not_applicable'] = intval($job['not_applicable'] ?? 0) + 1;
                $result_entry['action_result'] = 'not_applicable';
                $this->aysa_schema_inventory_upsert_row(intval($job['project_id'] ?? 0), $item, $row_before);
            } elseif (($action_plan['action'] ?? '') === 'needs_fields') {
                $job['needs_fields'] = intval($job['needs_fields'] ?? 0) + 1;
                $job['blocked_policy'] = intval($job['blocked_policy'] ?? 0) + 1;
                $job['blocked'] = intval($job['blocked'] ?? 0) + 1;
                $job['skipped'] = intval($job['skipped'] ?? 0) + 1;
                $result_entry['action_result'] = 'needs_fields';
                $this->aysa_schema_inventory_upsert_row(intval($job['project_id'] ?? 0), $item, $row_before);
            } elseif (($action_plan['action'] ?? '') === 'needs_takeover') {
                $job['needs_takeover'] = intval($job['needs_takeover'] ?? 0) + 1;
                $job['blocked_policy'] = intval($job['blocked_policy'] ?? 0) + 1;
                $job['blocked'] = intval($job['blocked'] ?? 0) + 1;
                $job['skipped'] = intval($job['skipped'] ?? 0) + 1;
                $result_entry['action_result'] = 'needs_takeover';
                $this->aysa_schema_inventory_upsert_row(intval($job['project_id'] ?? 0), $item, $row_before);
            } elseif (($action_plan['action'] ?? '') === 'blocked_policy') {
                $job['blocked_policy'] = intval($job['blocked_policy'] ?? 0) + 1;
                $job['blocked'] = intval($job['blocked'] ?? 0) + 1;
                $job['skipped'] = intval($job['skipped'] ?? 0) + 1;
                $result_entry['action_result'] = 'blocked_policy';
                $this->aysa_schema_inventory_upsert_row(intval($job['project_id'] ?? 0), $item, $row_before);
            } elseif (($action_plan['action'] ?? '') === 'blocked_output') {
                $job['blocked_output'] = intval($job['blocked_output'] ?? 0) + 1;
                $job['blocked'] = intval($job['blocked'] ?? 0) + 1;
                $job['skipped'] = intval($job['skipped'] ?? 0) + 1;
                $result_entry['action_result'] = 'blocked_output';
                $this->aysa_schema_inventory_upsert_row(intval($job['project_id'] ?? 0), $item, $row_before);
            } else {
                $mutating_action = in_array((string) ($action_plan['action'] ?? ''), array('generate_safe', 'rewrite_recommended'), true);
                if ($mutating_action) {
                    $preflight = $this->aysa_technical_schema_preflight_payload(
                        (string) ($action_plan['schema_type'] ?? ''),
                        array_merge($item, array(
                            'schema_type' => (string) ($action_plan['schema_type'] ?? ''),
                        ))
                    );
                    if (is_wp_error($preflight)) {
                        $classified = $this->aysa_technical_schema_classify_preflight_error(
                            $preflight,
                            $row_before,
                            (string) ($action_plan['schema_type'] ?? '')
                        );
                        if (($classified['action_result'] ?? '') === 'needs_fields') {
                            $job['needs_fields'] = intval($job['needs_fields'] ?? 0) + 1;
                            $job['blocked_policy'] = intval($job['blocked_policy'] ?? 0) + 1;
                            $job['blocked'] = intval($job['blocked'] ?? 0) + 1;
                            $job['skipped'] = intval($job['skipped'] ?? 0) + 1;
                            $result_entry['action_result'] = 'needs_fields';
                        } else {
                            $job['blocked_policy'] = intval($job['blocked_policy'] ?? 0) + 1;
                            $job['blocked'] = intval($job['blocked'] ?? 0) + 1;
                            $job['skipped'] = intval($job['skipped'] ?? 0) + 1;
                            $result_entry['action_result'] = 'blocked_policy';
                        }
                        $result_entry['message'] = (string) ($classified['message'] ?? 'Schema payload cannot be built for this URL.');
                        $this->aysa_schema_inventory_upsert_row(intval($job['project_id'] ?? 0), $item, $row_before);
                        if (count((array) ($job['results'] ?? array())) >= 100) {
                            $job['results'] = array_slice((array) $job['results'], -99);
                        }
                        $job['results'][] = $result_entry;
                        $processed_in_chunk++;
                        continue;
                    }
                    $result_entry['autofill_actions'] = array_values((array) (($preflight['page_context']['autofill_actions'] ?? array())));
                }

                $result = $this->aysa_technical_schema_run_action(
                    intval($job['project_id'] ?? 0),
                    (string) ($action_plan['action'] ?? 'validate'),
                    array_merge($item, array(
                        'schema_type' => (string) ($action_plan['schema_type'] ?? ''),
                    ))
                );

                if (is_wp_error($result)) {
                    $job['failed'] = intval($job['failed'] ?? 0) + 1;
                    $job['failed_payload'] = intval($job['failed_payload'] ?? 0) + 1;
                    $job['last_error'] = $result->get_error_message();
                    $job['failed_urls'][] = sanitize_text_field((string) ($item['url'] ?? ''));
                    $result_entry['action_result'] = 'failed';
                    $result_entry['message'] = $result->get_error_message();
                    $this->aysa_schema_inventory_upsert_row(intval($job['project_id'] ?? 0), $item, $row_before);
                } else {
                    $row_after = $this->aysa_technical_schema_get_single_row($item, true);
                    $this->aysa_schema_inventory_upsert_row(intval($job['project_id'] ?? 0), $item, $row_after);
                    $result_entry['validation_after'] = array(
                        'status' => sanitize_key((string) ($row_after['status'] ?? 'missing')),
                        'google_status' => sanitize_key((string) ($row_after['google_status'] ?? 'not_applicable')),
                        'google_target' => sanitize_text_field((string) ($row_after['google_target_label'] ?? ($row_after['google_target'] ?? ''))),
                        'semantic_status' => !empty($row_after['semantic_ready']) ? 'ready' : 'needs_update',
                        'semantic_schema' => sanitize_text_field((string) ($row_after['semantic_schema_label'] ?? '')),
                        'website_wide_status' => !empty($row_after['website_wide_ready']) ? 'ready' : 'needs_update',
                        'website_wide_schema' => sanitize_text_field((string) ($row_after['website_wide_schema_label'] ?? '')),
                        'detected_nodes' => array_values((array) ($row_after['detected_nodes'] ?? array())),
                        'required_fields_missing' => array_values((array) ($row_after['required_fields_missing'] ?? array())),
                        'google_required_missing' => array_values((array) ($row_after['google_required_missing'] ?? array())),
                    );
                    if ((string) ($row_after['google_status'] ?? '') === 'ready') {
                        $job['google_ready'] = intval($job['google_ready'] ?? 0) + 1;
                    }
                    if (!empty($row_after['semantic_ready'])) {
                        $job['semantic_ready'] = intval($job['semantic_ready'] ?? 0) + 1;
                    }
                    if (!empty($row_after['website_wide_ready'])) {
                        $job['website_wide_ready_after'] = intval($job['website_wide_ready_after'] ?? 0) + 1;
                    }
                    if (!empty($row_after['page_level_ready'])) {
                        $job['page_level_ready_after'] = intval($job['page_level_ready_after'] ?? 0) + 1;
                    }

                    $mutating_action = in_array((string) ($action_plan['action'] ?? ''), array('generate_safe', 'rewrite_recommended', 'disable_selected'), true);
                    $post_validation_passed = $this->aysa_technical_schema_post_validation_passes((string) ($job['mode'] ?? 'validate'), $row_after);
                    if ($mutating_action && ($action_plan['action'] ?? '') !== 'disable_selected' && !$post_validation_passed) {
                        $job['failed'] = intval($job['failed'] ?? 0) + 1;
                        $job['failed_post_validation'] = intval($job['failed_post_validation'] ?? 0) + 1;
                        $job['last_error'] = 'Post-validation failed for ' . sanitize_text_field((string) ($item['url'] ?? ''));
                        $job['failed_urls'][] = sanitize_text_field((string) ($item['url'] ?? ''));
                        $result_entry['action_result'] = 'failed_post_validation';
                        $result_entry['message'] = 'Schema was written but frontend validation did not reach the required clean rewrite state for this page family.';
                    } else {
                        $job['successful'] = intval($job['successful'] ?? 0) + 1;
                        switch ((string) ($action_plan['action'] ?? 'validate')) {
                            case 'generate_safe':
                                $job['generated'] = intval($job['generated'] ?? 0) + 1;
                                $result_entry['action_result'] = 'generated';
                                break;
                            case 'rewrite_recommended':
                                $job['rewritten'] = intval($job['rewritten'] ?? 0) + 1;
                                $job['overwritten'] = intval($job['overwritten'] ?? 0) + 1;
                                if (!empty($result_entry['autofill_actions'])) {
                                    $job['autofilled'] = intval($job['autofilled'] ?? 0) + 1;
                                    $result_entry['action_result'] = 'autofilled_then_overwritten';
                                    $result_entry['message'] = implode(' ', array_values((array) $result_entry['autofill_actions']));
                                } else {
                                    $result_entry['action_result'] = 'rewritten';
                                }
                                break;
                            case 'disable_selected':
                                $result_entry['action_result'] = 'disabled';
                                break;
                            default:
                                $job['validated'] = intval($job['validated'] ?? 0) + 1;
                                $result_entry['action_result'] = 'validated';
                                break;
                        }
                    }
                }
            }

            if (count((array) ($job['results'] ?? array())) >= 100) {
                $job['results'] = array_slice((array) $job['results'], -99);
            }
            $job['results'][] = $result_entry;
            $processed_in_chunk++;
        }

        $job['items'] = array_values($items);
        $job['lock_until'] = 0;
        if ((string) ($job['status'] ?? '') !== 'cancelled') {
            if (empty($job['items'])) {
                $job['status'] = intval($job['failed'] ?? 0) > 0 && intval($job['successful'] ?? 0) === 0 ? 'failed' : 'completed';
                $this->aysa_technical_schema_invalidate_caches(intval($job['project_id'] ?? 0));
                $this->aysa_schema_inventory_update_client_context(intval($job['project_id'] ?? 0));
            } else {
                $job['status'] = 'running';
            }
        }

        $job = $this->aysa_technical_schema_write_job($job_id, $job);
        if ((string) ($job['status'] ?? '') === 'running') {
            $this->aysa_technical_schema_schedule_job($job_id, 1);
        }

        return $job;
    }

    public function process_technical_schema_job_event($job_id) {
        $job = $this->aysa_technical_schema_process_job((string) $job_id, 12);
        if (is_array($job) && in_array((string) ($job['status'] ?? ''), array('completed', 'failed', 'cancelled'), true)) {
            $this->aysa_project_jobs_finalize(
                intval($job['project_id'] ?? 0),
                $this->aysa_project_jobs_make_worker_ref('technical_schema', 'schema_job', (string) ($job['job_id'] ?? $job_id)),
                ((string) ($job['status'] ?? '') === 'completed') ? 'completed' : 'failed',
                is_array($job) ? $job : null,
                (string) ($job['last_error'] ?? '')
            );
        }
    }

    public function process_technical_schema_rewrite_job_event($job_id) {
        $this->aysa_technical_schema_process_job((string) $job_id, 12);
    }

    private function aysa_technical_schema_collect_start_items($project_id, $mode, $scope, array $decoded_items) {
        $scope_lookup = $this->aysa_technical_schema_scope_lookup();
        $items = array();
        $entity_group = '';
        if (sanitize_key((string) $scope) === 'sitewide' && !empty($decoded_items[0]) && is_array($decoded_items[0])) {
            $entity_group = sanitize_key((string) ($decoded_items[0]['entity_group'] ?? ''));
        }

        if (sanitize_key((string) $scope) === 'sitewide') {
            foreach ($scope_lookup as $url => $page_data) {
                if (!is_array($page_data) || $url === '') {
                    continue;
                }
                if ($entity_group !== '' && sanitize_key((string) ($page_data['entity_group'] ?? '')) !== $entity_group) {
                    continue;
                }
                $items[$url] = array(
                    'url' => $url,
                    'title' => sanitize_text_field((string) ($page_data['title'] ?? $url)),
                    'page_family' => sanitize_key((string) ($page_data['page_family'] ?? 'page')),
                    'entity_group' => sanitize_key((string) ($page_data['entity_group'] ?? '')),
                    'schema_type' => sanitize_key((string) ($page_data['schema_type'] ?? '')),
                    'primary_recommended_node' => sanitize_key((string) ($page_data['schema_type'] ?? '')),
                );
            }
            return array_values($items);
        }

        foreach ($decoded_items as $row) {
            if (!is_array($row)) {
                continue;
            }
            $item = $this->aysa_technical_schema_resolve_scope_item($row, $scope_lookup);
            if (!empty($item['url'])) {
                if ($entity_group !== '' && sanitize_key((string) ($item['entity_group'] ?? '')) !== $entity_group) {
                    continue;
                }
                $items[$item['url']] = $item;
            }
        }

        return array_values($items);
    }

    private function aysa_technical_schema_create_job($project_id, $mode, $scope, array $items, $job_kind = 'worker') {
        $job_id = 'schema_' . sanitize_key((string) $mode) . '_' . wp_generate_password(8, false, false) . '_' . time();
        $entity_group = '';
        if (!empty($items[0]) && is_array($items[0])) {
            $entity_group = sanitize_key((string) ($items[0]['entity_group'] ?? ''));
        }
        $job = array(
            'job_id' => $job_id,
            'project_id' => intval($project_id),
            'mode' => sanitize_key((string) $mode),
            'entity_group' => $entity_group,
            'scope' => sanitize_key((string) $scope),
            'job_kind' => sanitize_key((string) $job_kind),
            'status' => 'queued',
            'total' => count($items),
            'processed' => 0,
            'successful' => 0,
            'failed' => 0,
            'skipped' => 0,
            'generated' => 0,
            'rewritten' => 0,
            'overwritten' => 0,
            'autofilled' => 0,
            'validated' => 0,
            'failed_payload' => 0,
            'needs_takeover' => 0,
            'needs_fields' => 0,
            'skipped_google_ready' => 0,
            'not_applicable' => 0,
            'failed_post_validation' => 0,
            'google_ready' => 0,
            'semantic_ready' => 0,
            'website_wide_ready_after' => 0,
            'page_level_ready_after' => 0,
            'blocked_policy' => 0,
            'blocked_output' => 0,
            'blocked' => 0,
            'conflict' => 0,
            'cached' => 0,
            'refetched' => 0,
            'items' => array_values($items),
            'results' => array(),
            'failed_urls' => array(),
            'last_error' => '',
            'created_at' => current_time('mysql'),
            'created_by' => get_current_user_id(),
            'lock_until' => 0,
        );

        return $this->aysa_technical_schema_write_job($job_id, $job);
    }

    public function handle_technical_schema_job_start() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $mode = isset($_POST['mode']) ? sanitize_key((string) wp_unslash($_POST['mode'])) : 'validate';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $scope = isset($_POST['scope']) ? sanitize_key((string) wp_unslash($_POST['scope'])) : 'selection';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $raw_items = isset($_POST['items']) ? wp_unslash($_POST['items']) : '[]';
        $decoded_items = is_string($raw_items) ? json_decode($raw_items, true) : (is_array($raw_items) ? $raw_items : array());
        if (!is_array($decoded_items)) {
            $decoded_items = array();
        }

        $items = $this->aysa_technical_schema_collect_start_items($project_id, $mode, $scope, $decoded_items);
        if (empty($items)) {
            wp_send_json_error('No valid schema rows were selected.');
            return;
        }

        $job = $this->aysa_project_jobs_enqueue(
            $project_id,
            'technical',
            'schema_job',
            array(
                'project_id' => $project_id,
                'mode' => $mode,
                'scope' => $scope,
                'items' => $items,
            ),
            array(
                'dedupe_key' => md5(wp_json_encode(array($project_id, $mode, $scope, $items))),
                'input_summary' => array(
                    'mode' => $mode,
                    'scope' => $scope,
                    'selected_count' => count($items),
                ),
            )
        );

        $worker_job = !empty($job['worker_ref']) ? $this->aysa_technical_schema_read_job((string) ($job['worker_ref'] !== '' ? preg_replace('/^technical_schema:schema_job:/', '', (string) $job['worker_ref']) : '')) : null;
        wp_send_json_success(array(
            'job' => is_array($worker_job) ? $this->aysa_technical_schema_get_job_response((array) $worker_job) : null,
            'started' => in_array((string) ($job['status'] ?? ''), array('queued', 'running', 'completed'), true),
            'project_job_status' => array(
                'run_id' => (string) ($job['job_id'] ?? ''),
                'status' => (string) ($job['status'] ?? 'queued'),
                'worker_ref' => (string) ($job['worker_ref'] ?? ''),
                'worker_status' => (string) ($job['worker_status'] ?? ''),
            ),
            'project_job_rack' => $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state($project_id)),
        ));
    }

    public function handle_technical_schema_job_status() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $job_id = isset($_POST['job_id']) ? sanitize_text_field((string) wp_unslash($_POST['job_id'])) : '';
        if ($job_id === '') {
            wp_send_json_error('Missing job id');
            return;
        }

        $job = $this->aysa_technical_schema_read_job($job_id);
        if (!$job) {
            wp_send_json_error('Schema job not found.');
            return;
        }
        if (intval($job['project_id'] ?? 0) !== $project_id) {
            wp_send_json_error('Schema job does not belong to this project.');
            return;
        }

        if ((string) ($job['status'] ?? '') === 'queued') {
            $job = $this->aysa_technical_schema_process_job($job_id, 2);
        } elseif ((string) ($job['status'] ?? '') === 'running' && intval($job['lock_until'] ?? 0) <= time()) {
            $job = $this->aysa_technical_schema_process_job($job_id, 2);
        }

        if (in_array((string) ($job['status'] ?? ''), array('completed', 'failed', 'cancelled'), true)) {
            $this->aysa_project_jobs_finalize(
                $project_id,
                $this->aysa_project_jobs_make_worker_ref('technical_schema', 'schema_job', (string) ($job['job_id'] ?? $job_id)),
                ((string) ($job['status'] ?? '') === 'completed') ? 'completed' : 'failed',
                is_array($job) ? $job : null,
                (string) ($job['last_error'] ?? '')
            );
        }

        wp_send_json_success(array(
            'job' => $this->aysa_technical_schema_get_job_response((array) $job),
            'project_job_rack' => $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state($project_id)),
        ));
    }

    public function handle_technical_schema_job_cancel() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $job_id = isset($_POST['job_id']) ? sanitize_text_field((string) wp_unslash($_POST['job_id'])) : '';
        $job = $job_id !== '' ? $this->aysa_technical_schema_read_job($job_id) : null;

        if (!$job || intval($job['project_id'] ?? 0) !== $project_id) {
            wp_send_json_error('Schema job not found.');
            return;
        }

        $job['status'] = 'cancelled';
        $job['lock_until'] = 0;
        $job = $this->aysa_technical_schema_write_job($job_id, $job);
        if ((string) ($job['job_kind'] ?? '') === 'snapshot_refresh') {
            delete_transient($this->aysa_technical_schema_refresh_job_pointer_key($project_id));
        }

        wp_send_json_success(array(
            'job' => $this->aysa_technical_schema_get_job_response((array) $job),
            'cancelled' => true,
        ));
    }

    public function handle_technical_schema_refresh_start() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        $this->aysa_adjustments_delete_technical_schema_snapshot_cache($project_id);
        $running_job = $this->aysa_technical_schema_get_current_refresh_job($project_id);
        if ($running_job && in_array((string) ($running_job['status'] ?? ''), array('queued', 'running'), true)) {
            wp_send_json_success(array(
                'job' => $this->aysa_technical_schema_get_job_response($running_job),
                'started' => false,
                'already_running' => true,
            ));
            return;
        }

        $items = array();
        foreach ((array) $this->aysa_adjustments_collect_schema_scope_pages() as $page_data) {
            if (!is_array($page_data)) {
                continue;
            }
            $url = $this->aysa_adjustments_normalize_schema_url((string) ($page_data['url'] ?? ''));
            if ($url === '') {
                continue;
            }
            $page_data['url'] = $url;
            $items[$url] = $page_data;
        }
        $items = array_values($items);
        if (empty($items)) {
            wp_send_json_error('No schema scope pages were found for snapshot refresh.');
            return;
        }

        $job = $this->aysa_technical_schema_create_job($project_id, 'snapshot_refresh', 'sitewide', $items, 'snapshot_refresh');
        set_transient($this->aysa_technical_schema_refresh_job_pointer_key($project_id), (string) $job['job_id'], 12 * HOUR_IN_SECONDS);
        $summary_state = $this->aysa_adjustments_get_cached_or_idle_technical_schema_summary($project_id);
        $summary_state['status'] = 'rebuilding';
        $summary_state['needs_refresh'] = false;
        set_transient(
            $this->aysa_adjustments_get_schema_cache_key($project_id, 'summary'),
            $summary_state,
            HOUR_IN_SECONDS
        );
        $this->aysa_technical_schema_schedule_job((string) $job['job_id'], 1);

        wp_send_json_success(array(
            'job' => $this->aysa_technical_schema_get_job_response((array) $job),
            'started' => true,
        ));
    }

    public function handle_technical_schema_refresh_status() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $job_id = isset($_POST['job_id']) ? sanitize_text_field((string) wp_unslash($_POST['job_id'])) : '';
        $job = $job_id !== '' ? $this->aysa_technical_schema_read_job($job_id) : $this->aysa_technical_schema_get_current_refresh_job($project_id);

        if (!$job || intval($job['project_id'] ?? 0) !== $project_id) {
            wp_send_json_error('Schema refresh job not found.');
            return;
        }

        if ((string) ($job['status'] ?? '') === 'queued') {
            $job = $this->aysa_technical_schema_process_job((string) ($job['job_id'] ?? ''), 4);
        } elseif ((string) ($job['status'] ?? '') === 'running' && intval($job['lock_until'] ?? 0) <= time()) {
            $job = $this->aysa_technical_schema_process_job((string) ($job['job_id'] ?? ''), 4);
        }

        wp_send_json_success(array(
            'job' => $this->aysa_technical_schema_get_job_response((array) $job),
        ));
    }

    private function aysa_internal_linking_workspace_service() {
        static $service = null;
        if (!$service && class_exists('Aysa_Internal_Linking_Workspace')) {
            $service = new Aysa_Internal_Linking_Workspace();
        }
        return $service;
    }

    private function aysa_onpage_workspace_service() {
        static $service = null;
        if (!$service && class_exists('Aysa_Onpage_Workspace')) {
            $service = new Aysa_Onpage_Workspace();
        }
        return $service;
    }

    private function aysa_run_response_matches_project($result, $project_id, $expected_run_id = 0) {
        if (!is_array($result) || intval($project_id) <= 0) {
            return false;
        }

        $scoped_payloads = array(
            $result,
            is_array($result['run'] ?? null) ? $result['run'] : array(),
            is_array($result['projection'] ?? null) ? $result['projection'] : array(),
        );
        foreach ($scoped_payloads as $payload) {
            $response_project_id = intval($payload['project_id'] ?? $payload['projectId'] ?? 0);
            if ($response_project_id > 0 && $response_project_id !== intval($project_id)) {
                return false;
            }
        }

        $expected_run_id = intval($expected_run_id);
        $response_run_id = intval($result['run']['id'] ?? $result['run_id'] ?? 0);
        if ($expected_run_id > 0 && $response_run_id > 0 && $response_run_id !== $expected_run_id) {
            return false;
        }

        return true;
    }

    private function aysa_internal_linking_workspace_filters_from_request() {
        // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $filters = isset($_POST['filters']) ? wp_unslash($_POST['filters']) : array();
        if (is_string($filters)) {
            $decoded = json_decode($filters, true);
            $filters = is_array($decoded) ? $decoded : array_filter(array_map('sanitize_key', explode(',', $filters)));
        }
        return is_array($filters) ? $filters : array();
    }

    private function aysa_onpage_workspace_filters_from_request() {
        // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $filters = isset($_POST['filters']) ? wp_unslash($_POST['filters']) : array();
        if (is_string($filters)) {
            $decoded = json_decode($filters, true);
            $filters = is_array($decoded) ? $decoded : array('view' => sanitize_key($filters));
        }
        return is_array($filters) ? $filters : array();
    }

    private function aysa_onpage_workspace_paging_from_request() {
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $page = isset($_POST['page']) ? intval((string) wp_unslash($_POST['page'])) : 1;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $per_page = isset($_POST['per_page']) ? intval((string) wp_unslash($_POST['per_page'])) : 50;

        return array(
            'page' => max(1, $page),
            'per_page' => max(1, min(100, $per_page)),
        );
    }

    public function handle_onpage_workspace() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        $service = $this->aysa_onpage_workspace_service();
        if (!$service) {
            wp_send_json_error('On-page workspace service unavailable.');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_resolve_installed_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $entity_group = isset($_POST['entity_group']) ? sanitize_key((string) wp_unslash($_POST['entity_group'])) : 'page';
        $filters = $this->aysa_onpage_workspace_filters_from_request();
        $paging = $this->aysa_onpage_workspace_paging_from_request();
        $keyword_mapping_sync = $this->aysa_keyword_mapping_ensure_local_associations($project_id, false);

        $workspace = $service->build_workspace($project_id, array(
            'entity_group' => $entity_group,
            'filters' => $filters,
            'page' => $paging['page'],
            'per_page' => $paging['per_page'],
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'refresh' => !empty($_POST['refresh']),
        ));

        wp_send_json_success(array(
            'keyword_mapping_sync' => $keyword_mapping_sync,
            'workspace' => $workspace,
            'keyword_page_map_snapshot_sync' => $this->aysa_agent8_sync_keyword_page_map_snapshot($project_id, 'onpage_workspace'),
        ));
    }

    public function handle_onpage_detail() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        $service = $this->aysa_onpage_workspace_service();
        if (!$service) {
            wp_send_json_error('On-page workspace service unavailable.');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_resolve_installed_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $entity_key = isset($_POST['entity_key']) ? sanitize_text_field((string) wp_unslash($_POST['entity_key'])) : '';

        wp_send_json_success(array(
            'detail' => $service->get_detail($project_id, $entity_key),
        ));
    }

    public function handle_onpage_start_run() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error(array('message' => 'Invalid nonce'));
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error(array('message' => 'Access denied'));
            return;
        }
        if (!$this->check_authentication(false)) {
            wp_send_json_error(array('message' => 'Authentication required.'));
            return;
        }

        $service = $this->aysa_onpage_workspace_service();
        if (!$service) {
            wp_send_json_error(array('message' => 'On-page workspace service unavailable.'));
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_resolve_installed_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Missing project ID.'));
            return;
        }
        $keyword_mapping_sync = $this->aysa_keyword_mapping_ensure_local_associations($project_id);

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $run_type = sanitize_key((string) wp_unslash($_POST['run_type'] ?? 'analyze'));
        $aliases = array(
            'analyze' => 'mapping_sync',
            'reconcile' => 'output_reconcile',
            'generate_meta_only' => 'meta_generate',
            'generate_content_rewrite' => 'rewrite_generate',
        );
        if (isset($aliases[$run_type])) {
            $run_type = $aliases[$run_type];
        }
        $allowed = array('mapping_sync', 'mapping_autofill', 'meta_generate', 'rewrite_decide', 'rewrite_generate', 'output_reconcile', 'keyword_validate_for_page');
        if (!in_array($run_type, $allowed, true)) {
            wp_send_json_error(array('message' => 'Unsupported on-page run type.'));
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $entity_group = isset($_POST['entity_group']) ? sanitize_key((string) wp_unslash($_POST['entity_group'])) : 'page';
        $filters = $this->aysa_onpage_workspace_filters_from_request();
        $paging = $this->aysa_onpage_workspace_paging_from_request();
        // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $selection = isset($_POST['selection']) ? wp_unslash($_POST['selection']) : array();
        if (!is_array($selection)) {
            $selection = array_filter(array_map('sanitize_text_field', explode(',', (string) $selection)));
        }

        $ad_hoc_page = array();
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        if (isset($_POST['ad_hoc_page'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
            $raw_ad_hoc_page = wp_unslash($_POST['ad_hoc_page']);
            if (is_string($raw_ad_hoc_page) && $raw_ad_hoc_page !== '') {
                $decoded = json_decode($raw_ad_hoc_page, true);
                if (is_array($decoded)) {
                    $raw_ad_hoc_page = $decoded;
                }
            }
            if (is_array($raw_ad_hoc_page)) {
                $ad_hoc_page = array(
                    'title' => isset($raw_ad_hoc_page['title']) ? sanitize_text_field((string) $raw_ad_hoc_page['title']) : '',
                    'meta_description' => isset($raw_ad_hoc_page['meta_description']) ? sanitize_textarea_field((string) $raw_ad_hoc_page['meta_description']) : '',
                    'page_text' => isset($raw_ad_hoc_page['page_text']) ? sanitize_textarea_field((string) $raw_ad_hoc_page['page_text']) : '',
                    'url' => isset($raw_ad_hoc_page['url']) ? esc_url_raw((string) $raw_ad_hoc_page['url']) : '',
                );
                $ad_hoc_page = array_filter($ad_hoc_page, static function ($value) {
                    return $value !== '';
                });
            }
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $language = isset($_POST['language']) ? sanitize_text_field((string) wp_unslash($_POST['language'])) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $country_code = isset($_POST['country_code']) ? sanitize_key((string) wp_unslash($_POST['country_code'])) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $location_code = isset($_POST['location_code']) ? intval((string) wp_unslash($_POST['location_code'])) : 0;

        $payload = array(
            'project_id' => $project_id,
            'run_type' => $run_type,
            'website' => home_url('/'),
            'seed_projection' => $service->seed_projection($project_id),
            'state_summary' => $service->build_workspace($project_id, array(
                'entity_group' => $entity_group,
                'filters' => $filters,
                'page' => $paging['page'],
                'per_page' => $paging['per_page'],
            ))['summary'] ?? array(),
            'selection_meta' => $service->build_run_selection_meta(
                $project_id,
                $entity_group !== '' ? $entity_group : 'all',
                $filters,
                array_values(array_map('sanitize_text_field', $selection))
            ),
            'selection' => array_values(array_map('sanitize_text_field', $selection)),
            'run_source' => 'plugin_onpage_workspace',
            'keyword_mapping_sync' => $keyword_mapping_sync,
        );

        if ($ad_hoc_page !== array()) {
            $payload['ad_hoc_page'] = $ad_hoc_page;
        }
        if ($language !== '') {
            $payload['language'] = $language;
        }
        if ($country_code !== '') {
            $payload['country_code'] = $country_code;
        }
        if ($location_code > 0) {
            $payload['location_code'] = $location_code;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        if (!empty($_POST['fast_lane'])) {
            $payload['fast_lane'] = true;
        }

        if (empty($payload['selection']) && !empty($payload['selection_meta']) && is_array($payload['selection_meta'])) {
            $payload['selection'] = array_values(array_filter(array_map(static function ($row): string {
                return is_array($row) ? sanitize_text_field((string) ($row['entity_key'] ?? '')) : '';
            }, $payload['selection_meta'])));
        }

        if ($payload['selection'] === array() && empty($payload['ad_hoc_page']) && $run_type === 'keyword_validate_for_page') {
            wp_send_json_error(array('message' => 'Keyword validation requires either an entity selection or ad hoc page content.'));
            return;
        }

        $result = $this->aysa_broker_request('POST', '/v1/onpage-runs/start', $payload, 45);
        if (is_wp_error($result)) {
            wp_send_json_error(array(
                'message' => 'On-page worker unavailable right now. Local fallback is disabled for this flow.',
                'broker_error' => $result->get_error_message(),
                'workspace' => $service->build_workspace($project_id, array(
                    'entity_group' => $entity_group,
                    'filters' => $filters,
                    'page' => $paging['page'],
                    'per_page' => $paging['per_page'],
                )),
            ));
            return;
        }
        if (!$this->aysa_run_response_matches_project($result, $project_id)) {
            wp_send_json_error(array('message' => 'On-page run response does not match this project.'));
            return;
        }

        if (!empty($result['run']['id'])) {
            $service->sync_remote_run_status(
                $project_id,
                (array) ($result['run'] ?? array()),
                isset($result['tasks']) && is_array($result['tasks']) ? $result['tasks'] : array()
            );
        }
        if (!empty($result['projection']) && !empty($result['run']['id'])) {
            $result['local_projection_sync'] = $service->apply_projection(
                $project_id,
                intval($result['run']['id']),
                (array) $result['projection']
            );
        }

        if (empty($result['deduped'])) {
            $billing_map = array(
                'mapping_sync' => array('action_key' => 'onpage_optimization_run', 'fallback_cost_cents' => 8),
                'mapping_autofill' => array('action_key' => 'onpage_mapping_autofill', 'fallback_cost_cents' => 12),
                'meta_generate' => array('action_key' => 'onpage_meta_generate', 'fallback_cost_cents' => 18),
                'rewrite_decide' => array('action_key' => 'onpage_rewrite_decide', 'fallback_cost_cents' => 10),
                'rewrite_generate' => array('action_key' => 'onpage_rewrite_generate', 'fallback_cost_cents' => 35),
                'output_reconcile' => array('action_key' => 'onpage_output_reconcile', 'fallback_cost_cents' => 6),
                'keyword_validate_for_page' => array('action_key' => 'onpage_keyword_validate_page', 'fallback_cost_cents' => 14),
            );
            $billing = $billing_map[$run_type] ?? $billing_map['mapping_sync'];
            $run_reference = !empty($result['run']['id'])
                ? 'onpage_run:' . intval($result['run']['id'])
                : 'onpage_run:' . $project_id . ':' . gmdate('YmdHis');
            $this->aysa_wallet_track_action_usage(
                $project_id,
                (string) $billing['action_key'],
                $run_reference,
                array(
                    'module' => 'on_page_optimization',
                    'run_type' => $run_type,
                    'entity_group' => $entity_group,
                    'selection_count' => count((array) $payload['selection']),
                    'has_ad_hoc_page' => !empty($payload['ad_hoc_page']),
                ),
                intval($billing['fallback_cost_cents']),
                '/v1/onpage-runs/start'
            );
        }

        wp_send_json_success(array(
            'status' => $result,
            'workspace' => $service->build_workspace($project_id, array(
                'entity_group' => $entity_group,
                'filters' => $filters,
                'page' => $paging['page'],
                'per_page' => $paging['per_page'],
            )),
        ));
    }

    public function handle_onpage_run_status() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error(array('message' => 'Invalid nonce'));
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error(array('message' => 'Access denied'));
            return;
        }
        if (!$this->check_authentication(false)) {
            wp_send_json_error(array('message' => 'Authentication required.'));
            return;
        }

        $service = $this->aysa_onpage_workspace_service();
        if (!$service) {
            wp_send_json_error(array('message' => 'On-page workspace service unavailable.'));
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_resolve_installed_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Invalid project id'));
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $run_id = isset($_POST['run_id']) ? intval((string) wp_unslash($_POST['run_id'])) : 0;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $after_event_id = isset($_POST['after_event_id']) ? intval((string) wp_unslash($_POST['after_event_id'])) : 0;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $entity_group = isset($_POST['entity_group']) ? sanitize_key((string) wp_unslash($_POST['entity_group'])) : 'page';
        $filters = $this->aysa_onpage_workspace_filters_from_request();
        $paging = $this->aysa_onpage_workspace_paging_from_request();

        $result = $this->aysa_broker_request('POST', '/v1/onpage-runs/status', array(
            'project_id' => $project_id,
            'run_id' => $run_id,
            'after_event_id' => $after_event_id,
        ), 30);
        if (is_wp_error($result)) {
            wp_send_json_error(array(
                'message' => 'On-page worker status is unavailable right now. Local fallback is disabled for this flow.',
                'broker_error' => $result->get_error_message(),
                'workspace' => $service->build_workspace($project_id, array(
                    'entity_group' => $entity_group,
                    'filters' => $filters,
                    'page' => $paging['page'],
                    'per_page' => $paging['per_page'],
                )),
            ));
            return;
        }
        if (!$this->aysa_run_response_matches_project($result, $project_id, $run_id)) {
            wp_send_json_error(array('message' => 'On-page run status does not match this project or run.'));
            return;
        }

        if (!empty($result['run']['id'])) {
            $service->sync_remote_run_status(
                $project_id,
                (array) ($result['run'] ?? array()),
                isset($result['tasks']) && is_array($result['tasks']) ? $result['tasks'] : array()
            );
        }
        if (!empty($result['projection']) && !empty($result['run']['id'])) {
            $result['local_projection_sync'] = $service->apply_projection(
                $project_id,
                intval($result['run']['id']),
                (array) $result['projection']
            );
        }

        wp_send_json_success(array(
            'status' => $result,
            'workspace' => $service->build_workspace($project_id, array(
                'entity_group' => $entity_group,
                'filters' => $filters,
                'page' => $paging['page'],
                'per_page' => $paging['per_page'],
            )),
        ));
    }

    public function handle_onpage_entity_action_v2() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error(array('message' => 'Invalid nonce'));
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error(array('message' => 'Access denied'));
            return;
        }

        $service = $this->aysa_onpage_workspace_service();
        if (!$service) {
            wp_send_json_error(array('message' => 'On-page workspace service unavailable.'));
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_resolve_installed_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Invalid project id'));
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $entity_key = isset($_POST['entity_key']) ? sanitize_text_field((string) wp_unslash($_POST['entity_key'])) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $row_action = sanitize_key((string) wp_unslash($_POST['row_action'] ?? 'refresh'));
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $entity_group = isset($_POST['entity_group']) ? sanitize_key((string) wp_unslash($_POST['entity_group'])) : 'page';
        $filters = $this->aysa_onpage_workspace_filters_from_request();
        $paging = $this->aysa_onpage_workspace_paging_from_request();

        if ($row_action === 'refresh') {
            $result = $service->refresh_entity($project_id, $entity_key);
            if (empty($result['ok'])) {
                wp_send_json_error($result);
                return;
            }
            wp_send_json_success(array(
                'result' => $result,
                'detail' => $service->get_detail($project_id, $entity_key),
                'workspace' => $service->build_workspace($project_id, array(
                    'entity_group' => $entity_group,
                    'filters' => $filters,
                    'page' => $paging['page'],
                    'per_page' => $paging['per_page'],
                )),
            ));
            return;
        }

        if ($row_action === 'accept_mapping') {
            $result = $service->accept_mapping_review($project_id, $entity_key);
            if (empty($result['ok'])) {
                wp_send_json_error($result);
                return;
            }
            $snapshot_sync = $this->aysa_agent8_sync_keyword_page_map_snapshot($project_id, 'onpage_mapping_accept');
            wp_send_json_success(array(
                'result' => $result,
                'detail' => $service->get_detail($project_id, $entity_key),
                'workspace' => $service->build_workspace($project_id, array(
                    'entity_group' => $entity_group,
                    'filters' => $filters,
                    'page' => $paging['page'],
                    'per_page' => $paging['per_page'],
                )),
                'keyword_page_map_snapshot_sync' => $snapshot_sync,
            ));
            return;
        }

        if ($row_action === 'reject_mapping') {
            $result = $service->reject_mapping_review($project_id, $entity_key);
            if (empty($result['ok'])) {
                wp_send_json_error($result);
                return;
            }
            $snapshot_sync = $this->aysa_agent8_sync_keyword_page_map_snapshot($project_id, 'onpage_mapping_reject');
            wp_send_json_success(array(
                'result' => $result,
                'detail' => $service->get_detail($project_id, $entity_key),
                'workspace' => $service->build_workspace($project_id, array(
                    'entity_group' => $entity_group,
                    'filters' => $filters,
                    'page' => $paging['page'],
                    'per_page' => $paging['per_page'],
                )),
                'keyword_page_map_snapshot_sync' => $snapshot_sync,
            ));
            return;
        }

        if ($row_action === 'apply_meta') {
            $result = $service->apply_meta_output($project_id, $entity_key);
            if (empty($result['ok'])) {
                wp_send_json_error($result);
                return;
            }
            wp_send_json_success(array(
                'result' => $result,
                'detail' => $service->get_detail($project_id, $entity_key),
                'workspace' => $service->build_workspace($project_id, array(
                    'entity_group' => $entity_group,
                    'filters' => $filters,
                    'page' => $paging['page'],
                    'per_page' => $paging['per_page'],
                )),
            ));
            return;
        }

        if ($row_action === 'apply_rewrite') {
            $result = $service->apply_rewrite_output($project_id, $entity_key);
            if (empty($result['ok'])) {
                wp_send_json_error($result);
                return;
            }
            wp_send_json_success(array(
                'result' => $result,
                'detail' => $service->get_detail($project_id, $entity_key),
                'workspace' => $service->build_workspace($project_id, array(
                    'entity_group' => $entity_group,
                    'filters' => $filters,
                    'page' => $paging['page'],
                    'per_page' => $paging['per_page'],
                )),
            ));
            return;
        }

        if ($row_action === 'skip') {
            $result = $service->skip_entity($project_id, $entity_key);
            if (empty($result['ok'])) {
                wp_send_json_error($result);
                return;
            }
            wp_send_json_success(array(
                'result' => $result,
                'detail' => $service->get_detail($project_id, $entity_key),
                'workspace' => $service->build_workspace($project_id, array(
                    'entity_group' => $entity_group,
                    'filters' => $filters,
                    'page' => $paging['page'],
                    'per_page' => $paging['per_page'],
                )),
            ));
            return;
        }

        $mapped_action = match ($row_action) {
            'generate_meta' => 'meta_generate',
            'generate_meta_only' => 'meta_generate',
            'validate_keywords' => 'keyword_validate_for_page',
            'rewrite_decide' => 'rewrite_decide',
            'generate_rewrite' => 'rewrite_generate',
            'generate_content_rewrite' => 'rewrite_generate',
            default => '',
        };
        if ($mapped_action === '') {
            wp_send_json_error(array('message' => 'Unsupported entity action.'));
            return;
        }

        $_POST['run_type'] = $mapped_action;
        $_POST['selection'] = array($entity_key);
        $_POST['fast_lane'] = '1';
        $this->handle_onpage_start_run();
    }

    public function handle_onpage_batch_action_v2() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error(array('message' => 'Invalid nonce'));
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error(array('message' => 'Access denied'));
            return;
        }

        $service = $this->aysa_onpage_workspace_service();
        if (!$service) {
            wp_send_json_error(array('message' => 'On-page workspace service unavailable.'));
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_resolve_installed_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Invalid project id'));
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $entity_group = isset($_POST['entity_group']) ? sanitize_key((string) wp_unslash($_POST['entity_group'])) : 'page';
        $filters = $this->aysa_onpage_workspace_filters_from_request();
        $paging = $this->aysa_onpage_workspace_paging_from_request();
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $batch_action = sanitize_key((string) wp_unslash($_POST['batch_action'] ?? ''));
        // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $selection = isset($_POST['selection']) ? wp_unslash($_POST['selection']) : array();
        if (!is_array($selection)) {
            $selection = array_filter(array_map('sanitize_text_field', explode(',', (string) $selection)));
        } else {
            $selection = array_values(array_filter(array_map('sanitize_text_field', $selection)));
        }

        $result = match ($batch_action) {
            'apply_meta' => $service->batch_apply_operation($project_id, $entity_group, $filters, $selection, 'apply_meta'),
            'apply_rewrite' => $service->batch_apply_operation($project_id, $entity_group, $filters, $selection, 'apply_rewrite'),
            'skip' => $service->batch_apply_operation($project_id, $entity_group, $filters, $selection, 'skip'),
            default => array('ok' => false, 'error' => 'unsupported_batch_action'),
        };

        if (empty($result['ok'])) {
            wp_send_json_error($result);
            return;
        }

        wp_send_json_success(array(
            'result' => $result,
            'workspace' => $service->build_workspace($project_id, array(
                'entity_group' => $entity_group,
                'filters' => $filters,
                'page' => $paging['page'],
                'per_page' => $paging['per_page'],
            )),
        ));
    }

    private function aysa_onpage_build_local_run_fallback($service, $project_id, $run_type, $entity_group, $filters = array()) {
        $workspace = $service->build_workspace($project_id, array(
            'entity_group' => $entity_group,
            'filters' => is_array($filters) ? $filters : array(),
            'page' => 1,
            'per_page' => 50,
            'refresh' => true,
        ));

        return array(
            'status' => array(
                'ok' => true,
                'run' => array(
                    'id' => 0,
                    'status' => 'completed',
                    'run_type' => $run_type,
                    'current_milestone' => 'Local workspace refresh completed',
                ),
                'events' => array(
                    array(
                        'id' => 0,
                        'event_type' => 'local_fallback',
                        'event_level' => 'warning',
                        'message_text' => 'Broker unavailable. Local workspace refresh was used instead.',
                    ),
                ),
                'tasks' => array(),
                'projection' => null,
            ),
            'workspace' => $workspace,
        );
    }

    public function handle_internal_linking_workspace() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        $service = $this->aysa_internal_linking_workspace_service();
        if (!$service) {
            wp_send_json_error('Internal linking workspace service unavailable.');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_resolve_installed_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        $filters = $this->aysa_internal_linking_workspace_filters_from_request();

        wp_send_json_success(array(
            'workspace' => $service->build_workspace($project_id, array(
                'filters' => $filters,
                // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
                'page' => isset($_POST['page']) ? intval(wp_unslash($_POST['page'])) : 1,
                // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
                'per_page' => isset($_POST['per_page']) ? intval(wp_unslash($_POST['per_page'])) : 50,
                // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
                'refresh' => !empty($_POST['refresh']),
            )),
        ));
    }

    public function handle_internal_linking_detail() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        $service = $this->aysa_internal_linking_workspace_service();
        if (!$service) {
            wp_send_json_error('Internal linking workspace service unavailable.');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_resolve_installed_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $opportunity_id = isset($_POST['opportunity_id']) ? intval((string) wp_unslash($_POST['opportunity_id'])) : 0;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $entity_key = isset($_POST['entity_key']) ? sanitize_text_field((string) wp_unslash($_POST['entity_key'])) : '';

        wp_send_json_success(array(
            'detail' => $service->get_detail($project_id, $opportunity_id, $entity_key),
        ));
    }

    public function handle_internal_linking_start_run() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error(array('message' => 'Invalid nonce'));
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error(array('message' => 'Access denied'));
            return;
        }
        if (!$this->check_authentication(false)) {
            wp_send_json_error(array('message' => 'Authentication required.'));
            return;
        }

        $service = $this->aysa_internal_linking_workspace_service();
        if (!$service) {
            wp_send_json_error(array('message' => 'Internal linking workspace service unavailable.'));
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_resolve_installed_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Missing project ID.'));
            return;
        }
        $keyword_mapping_sync = $this->aysa_keyword_mapping_ensure_local_associations($project_id);

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $run_type = sanitize_key((string) wp_unslash($_POST['run_type'] ?? 'analyze'));
        $filters = $this->aysa_internal_linking_workspace_filters_from_request();
        $allowed = array('analyze', 'apply_safe', 'reconcile', 'remove_aysa_links', 'rewrite_selected', 'target_revalidate');
        if (!in_array($run_type, $allowed, true)) {
            wp_send_json_error(array('message' => 'Unsupported internal linking run type.'));
            return;
        }

        $legacy_map = array(
            'analyze' => 'analyze',
            'apply_safe' => 'apply_safe',
            'reconcile' => 'analyze',
        );
        $legacy_result = null;
        if (isset($legacy_map[$run_type])) {
            $legacy_result = $service->run_legacy_action($project_id, $legacy_map[$run_type]);
            if (empty($legacy_result['ok'])) {
                wp_send_json_error(array(
                    'message' => (string) ($legacy_result['error'] ?? 'legacy_internal_linking_failed'),
                    'keyword_mapping_sync' => $keyword_mapping_sync,
                ));
                return;
            }
        }

        $payload = array(
            'project_id' => $project_id,
            'run_type' => $run_type,
            'website' => home_url('/'),
            'seed_projection' => is_array($legacy_result['projection'] ?? null) ? $legacy_result['projection'] : $service->seed_projection($project_id),
            // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
            'selection' => isset($_POST['selection']) ? wp_unslash($_POST['selection']) : array(),
            'run_source' => 'plugin_internal_linking_workspace',
        );

        $result = $this->aysa_broker_request('POST', '/v1/internal-link-runs/start', $payload, 45);
        if (is_wp_error($result)) {
            wp_send_json_error(array(
                'message' => $result->get_error_message(),
                'local_result' => $legacy_result,
            ));
            return;
        }
        if (!$this->aysa_run_response_matches_project($result, $project_id)) {
            wp_send_json_error(array('message' => 'Internal linking run response does not match this project.'));
            return;
        }

        if (!empty($result['projection']) && !empty($result['run']['id'])) {
            $result['local_projection_sync'] = $service->apply_projection(
                $project_id,
                intval($result['run']['id']),
                (array) $result['projection']
            );
        }

        wp_send_json_success(array(
            'status' => $result,
            'keyword_mapping_sync' => $keyword_mapping_sync,
            'workspace' => $service->build_workspace($project_id, array('page' => 1, 'per_page' => 50, 'filters' => $filters)),
        ));
    }

    public function handle_internal_linking_run_status() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error(array('message' => 'Invalid nonce'));
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error(array('message' => 'Access denied'));
            return;
        }
        if (!$this->check_authentication(false)) {
            wp_send_json_error(array('message' => 'Authentication required.'));
            return;
        }

        $service = $this->aysa_internal_linking_workspace_service();
        if (!$service) {
            wp_send_json_error(array('message' => 'Internal linking workspace service unavailable.'));
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_resolve_installed_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Invalid project id'));
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $run_id = isset($_POST['run_id']) ? intval((string) wp_unslash($_POST['run_id'])) : 0;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $after_event_id = isset($_POST['after_event_id']) ? intval((string) wp_unslash($_POST['after_event_id'])) : 0;
        $filters = $this->aysa_internal_linking_workspace_filters_from_request();

        $result = $this->aysa_broker_request('POST', '/v1/internal-link-runs/status', array(
            'project_id' => $project_id,
            'run_id' => $run_id,
            'after_event_id' => $after_event_id,
        ), 30);
        if (is_wp_error($result)) {
            wp_send_json_error(array('message' => $result->get_error_message()));
            return;
        }
        if (!$this->aysa_run_response_matches_project($result, $project_id, $run_id)) {
            wp_send_json_error(array('message' => 'Internal linking run status does not match this project or run.'));
            return;
        }

        if (!empty($result['projection']) && !empty($result['run']['id'])) {
            $result['local_projection_sync'] = $service->apply_projection(
                $project_id,
                intval($result['run']['id']),
                (array) $result['projection']
            );
        }

        wp_send_json_success(array(
            'status' => $result,
            'workspace' => $service->build_workspace($project_id, array('page' => 1, 'per_page' => 50, 'filters' => $filters)),
        ));
    }

    public function handle_internal_linking_row_action_v2() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        $service = $this->aysa_internal_linking_workspace_service();
        if (!$service) {
            wp_send_json_error('Internal linking workspace service unavailable.');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_resolve_installed_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        $filters = $this->aysa_internal_linking_workspace_filters_from_request();
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $opportunity_id = isset($_POST['opportunity_id']) ? intval((string) wp_unslash($_POST['opportunity_id'])) : 0;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $row_action = sanitize_key((string) wp_unslash($_POST['row_action'] ?? 'validate'));
        $result = $service->update_row_action($project_id, $opportunity_id, $row_action);
        if (empty($result['ok'])) {
            wp_send_json_error(is_array($result) ? $result : array('message' => 'Unable to update row.'));
            return;
        }

        wp_send_json_success(array(
            'result' => $result,
            'workspace' => $service->build_workspace($project_id, array('page' => 1, 'per_page' => 50, 'filters' => $filters)),
        ));
    }

    private function aysa_adjustments_get_technical_internal_linking_snapshot($project_id) {
        $legacy_log = get_option('aysa_internal_linking_log', array());
        $legacy_rows = $this->aysa_adjustments_internal_linking_legacy_rows($legacy_log, 500);
        $service = $this->aysa_internal_linking_workspace_service();
        if ($service) {
            $workspace = $service->build_workspace(intval($project_id), array('page' => 1, 'per_page' => 100));
            $summary = is_array($workspace['summary'] ?? null) ? $workspace['summary'] : array();
            $rows = is_array($workspace['rows'] ?? null) ? $workspace['rows'] : array();
            $run = is_array($workspace['run'] ?? null) ? $workspace['run'] : array();
            $preview_package = method_exists($service, 'build_preview_package') ? $service->build_preview_package(intval($project_id)) : array();

            $overview_rows = array(
                array('metric' => 'Inventory', 'value' => intval($summary['all'] ?? 0)),
                array('metric' => 'Ready to apply', 'value' => intval($summary['ready_to_apply'] ?? 0)),
                array('metric' => 'Applied by AYSA', 'value' => intval($summary['applied_by_aysa'] ?? 0)),
                array('metric' => 'Needs review', 'value' => intval($summary['needs_review'] ?? 0)),
                array('metric' => 'Conflict', 'value' => intval($summary['conflict'] ?? 0)),
                array('metric' => 'Manual only', 'value' => intval($summary['manual_only'] ?? 0)),
            );

            $preview_items = isset($preview_package['items']) && is_array($preview_package['items']) ? $preview_package['items'] : array();
            $preview_items = $this->aysa_adjustments_internal_linking_hydrate_flat_preview_rows($preview_items, 80);
            if (isset($preview_package['items']) && is_array($preview_package['items'])) {
                $preview_package['items'] = $preview_items;
            }
            if (!empty($rows) || !empty($preview_items)) {
                return array(
                    'id' => 'technical_internal_linking',
                    'status' => !empty($rows) ? 'ready' : 'idle',
                    'enabled' => true,
                    'matching_type' => 'workspace_v2',
                    'count' => intval($summary['all'] ?? count($rows)),
                    'total_links' => intval($summary['applied_by_aysa'] ?? 0),
                    'content_items_affected' => intval($summary['all'] ?? 0),
                    'rows' => $rows,
                    'legacy_rows' => $legacy_rows,
                    'legacy_rows_found' => count($legacy_rows),
                    'preview_rows' => $preview_items,
                    'preview_keywords_found' => intval($summary['ready_to_apply'] ?? 0),
                    'preview_links_to_add' => intval($summary['ready_to_apply'] ?? 0),
                    'preview_content_items_scanned' => intval($summary['all'] ?? 0),
                    'preview_summary' => sanitize_text_field((string) ($run['current_milestone'] ?? '')),
                    'preview_package' => $preview_package,
                    'has_preview' => intval($summary['ready_to_apply'] ?? 0) > 0,
                    'overview_rows' => $overview_rows,
                    'has_data' => !empty($rows),
                    'workspace' => $workspace,
                );
            }
        }

        $default_settings = array(
            'enabled' => false,
            'matching_type' => 'both',
            'process_posts' => true,
            'process_pages' => true,
            'process_categories' => true,
            'process_products' => true,
            'case_sensitive' => false,
            'ignore_existing_links' => true,
            'priority_by_authority' => true,
        );
        $settings = wp_parse_args(get_option('aysa_internal_linking_settings', array()), $default_settings);
        $log = $legacy_log;
        $preview_state = get_option($this->aysa_technical_internal_linking_preview_state_key($project_id), array());
        $preview_data = is_array($preview_state) && isset($preview_state['data']) && is_array($preview_state['data']) ? $preview_state['data'] : array();

        $grouped = array();
        $affected_sources = array();
        foreach (is_array($log) ? $log : array() as $entry) {
            if (!is_array($entry)) {
                continue;
            }
            $keyword = sanitize_text_field((string) ($entry['keyword'] ?? ''));
            if ($keyword === '') {
                continue;
            }
            if (!isset($grouped[$keyword])) {
                $grouped[$keyword] = array(
                    'keyword' => $keyword,
                    'target_title' => sanitize_text_field((string) ($entry['target_title'] ?? '')),
                    'target_url' => esc_url_raw((string) ($entry['target_url'] ?? '')),
                    'total_links' => 0,
                    'sources_count' => 0,
                    'last_placement' => '',
                    '_sources' => array(),
                );
            }

            $grouped[$keyword]['total_links']++;
            $source_key = sanitize_text_field((string) ($entry['source_title'] ?? '')) . '|' . esc_url_raw((string) ($entry['source_url'] ?? ''));
            if ($source_key !== '|' && !isset($grouped[$keyword]['_sources'][$source_key])) {
                $grouped[$keyword]['_sources'][$source_key] = true;
            }
            $timestamp = sanitize_text_field((string) ($entry['timestamp'] ?? ''));
            if ($timestamp !== '' && ($grouped[$keyword]['last_placement'] === '' || strcmp($timestamp, $grouped[$keyword]['last_placement']) > 0)) {
                $grouped[$keyword]['last_placement'] = $timestamp;
            }
            if ($source_key !== '|') {
                $affected_sources[$source_key] = true;
            }
        }

        $rows = array();
        foreach ($grouped as $row) {
            $row['sources_count'] = count($row['_sources']);
            unset($row['_sources']);
            $rows[] = $row;
        }
        usort($rows, static function($left, $right) {
            return intval($right['total_links'] ?? 0) <=> intval($left['total_links'] ?? 0);
        });

        $preview_rows = array();
        $preview_changes = isset($preview_data['preview_changes']) && is_array($preview_data['preview_changes']) ? $preview_data['preview_changes'] : array();
        $hydrated_preview_changes = $this->aysa_adjustments_internal_linking_hydrate_grouped_preview_changes($preview_changes, 80);
        foreach ($hydrated_preview_changes as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keywords = isset($row['changes']) && is_array($row['changes']) ? array_values(array_filter(array_map(function($change) {
                return is_array($change) ? sanitize_text_field((string) ($change['keyword'] ?? '')) : '';
            }, $row['changes']))) : array();
            $targets = isset($row['changes']) && is_array($row['changes']) ? array_values(array_filter(array_map(function($change) {
                return is_array($change) ? sanitize_text_field((string) ($change['target_title'] ?? '')) : '';
            }, $row['changes']))) : array();

            $preview_rows[] = array_merge($row, array(
                'content_title' => sanitize_text_field((string) ($row['content_title'] ?? '')),
                'content_type' => sanitize_text_field((string) ($row['content_type'] ?? '')),
                'links_count' => intval($row['links_count'] ?? 0),
                'keywords' => implode(', ', array_slice($keywords, 0, 5)),
                'targets' => implode(', ', array_slice(array_unique($targets), 0, 3)),
            ));
        }

        $total_links = array_sum(array_map(static fn($row) => intval($row['total_links'] ?? 0), $rows));

        return array(
            'id' => 'technical_internal_linking',
            'status' => (!empty($rows) || !empty($preview_rows)) ? 'ready' : 'idle',
            'enabled' => !empty($settings['enabled']),
            'matching_type' => sanitize_text_field((string) ($settings['matching_type'] ?? 'both')),
            'count' => count($rows),
            'total_links' => $total_links,
            'content_items_affected' => count($affected_sources),
            'rows' => $rows,
            'legacy_rows' => $legacy_rows,
            'legacy_rows_found' => count($legacy_rows),
            'preview_rows' => $preview_rows,
            'preview_keywords_found' => intval($preview_data['total_keywords_found'] ?? 0),
            'preview_links_to_add' => intval($preview_data['links_to_be_added'] ?? 0),
            'preview_content_items_scanned' => intval($preview_data['content_items_scanned'] ?? 0),
            'preview_summary' => sanitize_text_field((string) ($preview_data['summary'] ?? '')),
            'has_preview' => !empty($preview_rows),
            'overview_rows' => array(
                array('metric' => 'Enabled', 'value' => !empty($settings['enabled']) ? 'Yes' : 'No'),
                array('metric' => 'Matching type', 'value' => sanitize_text_field((string) ($settings['matching_type'] ?? 'both'))),
                array('metric' => 'Keywords linked', 'value' => count($rows)),
                array('metric' => 'Links added', 'value' => $total_links),
                array('metric' => 'Affected content items', 'value' => count($affected_sources)),
                array('metric' => 'Preview links to add', 'value' => intval($preview_data['links_to_be_added'] ?? 0)),
            ),
            'has_data' => !empty($rows) || !empty($preview_rows),
        );
    }

    private function aysa_adjustments_internal_linking_legacy_rows($log, $limit = 500) {
        $rows = array();
        foreach (is_array($log) ? $log : array() as $index => $entry) {
            if (!is_array($entry)) {
                continue;
            }
            $keyword = sanitize_text_field((string) ($entry['keyword'] ?? ''));
            $target_url = esc_url_raw((string) ($entry['target_url'] ?? ''));
            $source_id = intval($entry['source_entity_id'] ?? 0);
            $source_type = sanitize_key((string) ($entry['source_entity_type'] ?? ''));
            $source_url = esc_url_raw((string) ($entry['source_url'] ?? ''));
            if ($source_url === '' && $source_id > 0 && in_array($source_type, array('post', 'page', 'product'), true)) {
                $permalink = get_permalink($source_id);
                $source_url = is_string($permalink) ? esc_url_raw($permalink) : '';
            }
            if ($keyword === '' && $target_url === '' && $source_url === '') {
                continue;
            }

            $rows[] = array(
                'row_id' => 'legacy_internal_linking_' . substr(hash('sha256', $source_id . '|' . $source_type . '|' . $target_url . '|' . $keyword . '|' . $index), 0, 20),
                'source_post_id' => in_array($source_type, array('post', 'page', 'product'), true) ? $source_id : 0,
                'source_entity_id' => $source_id,
                'source_entity_type' => $source_type,
                'source_url' => $source_url,
                'source_page_title' => sanitize_text_field((string) ($entry['source_title'] ?? '')),
                'source_title' => sanitize_text_field((string) ($entry['source_title'] ?? '')),
                'target_post_id' => $target_url !== '' ? intval(url_to_postid($target_url)) : 0,
                'target_url' => $target_url,
                'target_page_title' => sanitize_text_field((string) ($entry['target_title'] ?? '')),
                'target_title' => sanitize_text_field((string) ($entry['target_title'] ?? '')),
                'proposed_anchor_text' => $keyword,
                'anchor_text' => $keyword,
                'keyword' => $keyword,
                'target_primary_keyword' => $keyword,
                'match_type' => !empty($entry['is_partial']) ? 'legacy_partial_keyword_match' : 'legacy_exact_keyword_match',
                'status' => 'legacy_applied_readback',
                'legacy_source' => 'aysa_internal_linking_log',
                'existing_link_present' => true,
                'already_linked' => true,
                'target_status' => 'unknown',
                'target_indexability' => 'unknown',
                'last_placement' => sanitize_text_field((string) ($entry['timestamp'] ?? '')),
            );

            if (count($rows) >= $limit) {
                break;
            }
        }
        return $rows;
    }

    private function aysa_adjustments_internal_linking_hydrate_grouped_preview_changes($preview_changes, $limit = 80) {
        $rows = array();
        $processed = 0;
        foreach (is_array($preview_changes) ? $preview_changes : array() as $row) {
            if (!is_array($row)) {
                continue;
            }
            $source_post_id = intval($row['content_id'] ?? $row['source_content_id'] ?? $row['source_post_id'] ?? 0);
            $source_url = esc_url_raw((string) ($row['source_url'] ?? $row['source_content_url'] ?? ''));
            $changes = isset($row['changes']) && is_array($row['changes']) ? array_values($row['changes']) : array();
            $hydrated_changes = array();
            foreach ($changes as $change) {
                if (!is_array($change)) {
                    continue;
                }
                if ($processed >= intval($limit)) {
                    $hydrated_changes[] = $change;
                    continue;
                }
                $candidate = array_merge($change, array(
                    'source_post_id' => $source_post_id,
                    'source_content_id' => $source_post_id,
                    'source_content_type' => sanitize_key((string) ($row['content_type'] ?? $change['source_content_type'] ?? 'post')),
                    'source_url' => $source_url,
                    'source_content_url' => $source_url,
                    'source_page_title' => sanitize_text_field((string) ($row['content_title'] ?? $change['source_page_title'] ?? '')),
                    'source_title' => sanitize_text_field((string) ($row['content_title'] ?? $change['source_title'] ?? '')),
                    'proposed_anchor_text' => sanitize_text_field((string) ($change['proposed_anchor_text'] ?? $change['anchor_text'] ?? $change['keyword'] ?? '')),
                    'anchor_text' => sanitize_text_field((string) ($change['anchor_text'] ?? $change['keyword'] ?? $change['proposed_anchor_text'] ?? '')),
                ));
                $hydrated_changes[] = $this->aysa_adjustments_internal_linking_hydrate_apply_preview_row($candidate);
                $processed++;
            }
            $row['changes'] = $hydrated_changes;
            $rows[] = $row;
        }
        return $rows;
    }

    private function aysa_adjustments_internal_linking_hydrate_flat_preview_rows($preview_rows, $limit = 80) {
        $rows = array();
        $processed = 0;
        foreach (is_array($preview_rows) ? $preview_rows : array() as $row) {
            if (!is_array($row)) {
                continue;
            }
            if ($processed >= intval($limit)) {
                $rows[] = $row;
                continue;
            }
            $rows[] = $this->aysa_adjustments_internal_linking_hydrate_apply_preview_row($row);
            $processed++;
        }
        return $rows;
    }

    private function aysa_adjustments_internal_linking_hydrate_apply_preview_row($row) {
        $row = is_array($row) ? $row : array();
        $anchor = sanitize_text_field((string) ($row['proposed_anchor_text'] ?? $row['anchor_text'] ?? $row['keyword'] ?? ''));
        $target_url = esc_url_raw((string) ($row['target_url'] ?? $row['target_page_url'] ?? $row['to_url'] ?? ''));
        $source_post_id = intval($row['source_post_id'] ?? $row['source_content_id'] ?? $row['content_id'] ?? $row['post_id'] ?? 0);
        $source_url = esc_url_raw((string) ($row['source_url'] ?? $row['source_content_url'] ?? $row['url'] ?? ''));
        if ($source_post_id <= 0 && $source_url !== '') {
            $source_post_id = intval(url_to_postid($source_url));
        }
        if ($source_url === '' && $source_post_id > 0) {
            $permalink = get_permalink($source_post_id);
            $source_url = is_string($permalink) ? esc_url_raw($permalink) : '';
        }
        $target_post_id = intval($row['target_post_id'] ?? $row['target_id'] ?? 0);
        if ($target_post_id <= 0 && $target_url !== '') {
            $target_post_id = intval(url_to_postid($target_url));
        }

        $row['source_post_id'] = $source_post_id;
        $row['source_url'] = $source_url;
        $row['target_post_id'] = $target_post_id;
        $row['target_url'] = $target_url;
        $row['proposed_anchor_text'] = $anchor;
        $row['anchor_text'] = $anchor;
        $row['target_primary_keyword'] = sanitize_text_field((string) ($row['target_primary_keyword'] ?? $row['keyword'] ?? $anchor));
        $row['keyword_relevance_reason'] = sanitize_text_field((string) ($row['keyword_relevance_reason'] ?? 'Legacy Internal Linking preview matched this anchor to the target page.'));
        $row['accepted_by_user'] = false;
        $row['legacy_apply_preview_source'] = 'wp_content_readback';

        $blocked = $this->aysa_adjustments_internal_linking_apply_preview_blocker($source_post_id, $source_url, $target_post_id, $target_url, $anchor);
        if ($blocked !== '') {
            $row['apply_preview_status'] = 'blocked';
            $row['apply_preview_blocker'] = $blocked;
            if ($blocked === 'duplicate_link_exists') {
                $row['existing_link_present'] = true;
                $row['already_linked'] = true;
            }
            return $row;
        }

        $content = (string) get_post_field('post_content', $source_post_id);
        $snippet = $this->aysa_adjustments_internal_linking_find_safe_anchor_snippet($content, $anchor);
        if (empty($snippet['ok'])) {
            $fallback_candidates = $this->aysa_adjustments_internal_linking_anchor_fallback_candidates($anchor, sanitize_text_field((string) ($row['target_page_title'] ?? $row['target_title'] ?? '')));
            if ($fallback_candidates !== array()) {
                $snippet = $this->aysa_adjustments_internal_linking_find_safe_anchor_candidate_snippet($content, $fallback_candidates);
            }
        }
        if (empty($snippet['ok'])) {
            $snippet = $this->aysa_adjustments_internal_linking_contextual_append_snippet($content, $anchor, $target_url);
        }
        if (empty($snippet['ok'])) {
            $row['apply_preview_status'] = 'review';
            $row['apply_preview_blocker'] = sanitize_key((string) ($snippet['reason'] ?? 'blocked_no_safe_anchor_or_append'));
            return $row;
        }

        $current_snippet = (string) ($snippet['current_html_snippet'] ?? '');
        $matched_anchor = sanitize_text_field((string) ($snippet['matched_anchor'] ?? $anchor));
        $proposed_snippet = (string) ($snippet['proposed_html_snippet'] ?? '');
        if ($proposed_snippet === '') {
            $linked_anchor = '<a href="' . esc_url($target_url) . '" data-aysa-internal-link="1">' . esc_html($matched_anchor) . '</a>';
            $proposed_snippet = $this->aysa_agent8v3_replace_first($current_snippet, $matched_anchor, $linked_anchor);
        }
        if ($proposed_snippet === $current_snippet || strpos($proposed_snippet, '<a ') === false) {
            $row['apply_preview_status'] = 'review';
            $row['apply_preview_blocker'] = 'exact_diff_required';
            return $row;
        }

        $density = $this->aysa_agent8v3_internal_linking_density_snapshot($source_post_id, $content, 1);
        $match_type = sanitize_key((string) ($snippet['match_type'] ?? 'exact'));
        $insertion_method = sanitize_key((string) ($snippet['insertion_method'] ?? 'link_existing_phrase'));
        $row['apply_preview_status'] = 'apply_ready_preview';
        $row['current_html_snippet'] = $current_snippet;
        $row['proposed_html_snippet'] = $proposed_snippet;
        $row['surrounding_context_before'] = sanitize_text_field(wp_strip_all_tags($current_snippet));
        $row['surrounding_context_after'] = '';
        $row['diff_summary'] = $insertion_method === 'append_contextual_sentence'
            ? 'Append one approved contextual sentence with an internal link.'
            : 'Insert one approved contextual internal link in the matched paragraph.';
        $row['before_content_hash'] = hash('sha256', $current_snippet);
        $row['after_content_hash'] = hash('sha256', $proposed_snippet);
        $row['insertion_location_hash'] = hash('sha256', $source_url . '|' . $target_url . '|' . $matched_anchor . '|' . $current_snippet);
        $row['selected_anchor'] = $matched_anchor;
        $row['selected_anchor_strategy'] = $match_type;
        $row['match_type'] = $match_type;
        $row['insertion_method'] = $insertion_method;
        $row['source_sentence'] = sanitize_text_field((string) ($snippet['source_sentence'] ?? wp_strip_all_tags($current_snippet)));
        $row['anchor_confidence'] = sanitize_key((string) ($snippet['confidence'] ?? ($match_type === 'conservative_single' ? 'low' : 'high')));
        $row['anchor_risk'] = sanitize_key((string) ($snippet['risk'] ?? ($match_type === 'conservative_single' ? 'medium' : 'low')));
        if ($match_type === 'conservative_single') {
            $row['requires_human_review_reason'] = 'single_word_anchor';
            $row['final_relevance_score'] = min(79, intval($row['final_relevance_score'] ?? $row['confidence'] ?? 79));
        }
        $row['target_status'] = '200';
        $row['target_indexability'] = 'indexable';
        $row['target_canonical_url'] = esc_url_raw((string) get_permalink($target_post_id));
        $row['existing_link_present'] = false;
        $row['already_linked'] = false;
        $row['source_primary_keyword'] = sanitize_text_field((string) ($row['source_primary_keyword'] ?? $row['keyword'] ?? $matched_anchor));
        if ($match_type !== 'conservative_single') {
            $row['final_relevance_score'] = max($insertion_method === 'append_contextual_sentence' ? 90 : 88, intval($row['final_relevance_score'] ?? $row['confidence'] ?? 88));
        }
        $row['existing_contextual_internal_links_count'] = intval($density['existing_contextual_internal_links_count'] ?? 0);
        $row['existing_aysa_internal_links_count'] = intval($density['existing_aysa_internal_links_count'] ?? 0);
        $row['proposed_new_links_count'] = intval($density['proposed_new_links_count'] ?? 1);
        $row['projected_contextual_internal_links_count'] = intval($density['projected_contextual_internal_links_count'] ?? 0);
        $row['max_contextual_internal_links_allowed'] = intval($density['max_contextual_internal_links_allowed'] ?? 4);
        $row['recommended_contextual_internal_links_max'] = intval($density['recommended_contextual_internal_links_max'] ?? 3);
        $row['density_status'] = sanitize_key((string) ($density['density_status'] ?? 'unknown'));
        return $row;
    }

    private function aysa_adjustments_internal_linking_apply_preview_blocker($source_post_id, $source_url, $target_post_id, $target_url, $anchor) {
        $source_post_id = intval($source_post_id);
        $target_post_id = intval($target_post_id);
        $source_url = esc_url_raw((string) $source_url);
        $target_url = esc_url_raw((string) $target_url);
        $anchor = sanitize_text_field((string) $anchor);
        if ($source_post_id <= 0 || !get_post($source_post_id)) {
            return 'source_post_id_missing';
        }
        if ($source_url === '') {
            return 'source_url_missing';
        }
        if ($target_url === '') {
            return 'target_url_missing';
        }
        if ($target_post_id <= 0 || !get_post($target_post_id)) {
            return 'target_post_id_missing';
        }
        if ($anchor === '') {
            return 'anchor_missing';
        }
        if (!$this->aysa_agent8v3_internal_linking_same_site_url($source_url) || !$this->aysa_agent8v3_internal_linking_same_site_url($target_url)) {
            return 'target_not_internal';
        }
        if ($this->aysa_agent8v3_normalize_url_for_canonical($source_url) === $this->aysa_agent8v3_normalize_url_for_canonical($target_url)) {
            return 'same_page_link';
        }
        if (!in_array(get_post_status($source_post_id), array('publish'), true) || !in_array(get_post_status($target_post_id), array('publish'), true)) {
            return 'source_or_target_not_published';
        }
        if (!in_array(get_post_type($source_post_id), array('post', 'page'), true) || !in_array(get_post_type($target_post_id), array('post', 'page'), true)) {
            return 'unsupported_post_type';
        }
        $content = (string) get_post_field('post_content', $source_post_id);
        if (function_exists('has_blocks') && has_blocks($content)) {
            return 'unsupported_editor';
        }
        if ($this->aysa_agent8v3_internal_linking_content_has_target($content, $target_url)) {
            return 'duplicate_link_exists';
        }
        if ($this->aysa_agent8v3_internal_linking_generic_anchor($anchor)) {
            return 'generic_or_missing_anchor';
        }
        if ($this->aysa_agent8v3_post_has_noindex_signal($target_post_id) || !$this->aysa_agent8v3_internal_linking_target_self_canonical($target_post_id, $target_url)) {
            return 'target_not_indexable';
        }
        $target_status = $this->aysa_agent8v3_internal_linking_url_status($target_url);
        if ($target_status !== 200) {
            return 'target_not_200';
        }
        $density = $this->aysa_agent8v3_internal_linking_density_snapshot($source_post_id, $content, 1);
        if (!empty($density['overlinking_risk']) || (string) ($density['density_status'] ?? '') === 'over_limit') {
            return 'overlinking_risk';
        }
        return '';
    }

    private function aysa_adjustments_internal_linking_anchor_fallback_candidates($anchor, $target_title = '') {
        $anchor = trim(sanitize_text_field((string) $anchor));
        $target_title = trim(sanitize_text_field((string) $target_title));
        $phrases = array($anchor, $target_title);
        $tokens = preg_split('/\s+/u', mb_strtolower(remove_accents($anchor))) ?: array();
        $tokens = array_values(array_filter(array_map('trim', $tokens), static fn($token) => $token !== '' && mb_strlen($token) > 1));
        $candidates = array();
        $add = static function ($text, $match_type, $confidence, $risk) use (&$candidates): void {
            $text = trim(sanitize_text_field((string) $text));
            if ($text === '') {
                return;
            }
            $key = mb_strtolower(remove_accents($text));
            if (isset($candidates[$key])) {
                return;
            }
            $candidates[$key] = array(
                'anchor_text' => $text,
                'match_type' => $match_type,
                'confidence' => $confidence,
                'risk' => $risk,
            );
        };

        foreach ($phrases as $phrase) {
            $phrase = trim((string) $phrase);
            if ($phrase !== '' && mb_strtolower(remove_accents($phrase)) !== mb_strtolower(remove_accents($anchor))) {
                $add($phrase, 'strong_partial', 'medium', 'low');
            }
        }
        if (count($tokens) >= 3) {
            for ($i = 0; $i <= count($tokens) - 2; $i++) {
                $add(implode(' ', array_slice($tokens, $i, 2)), 'strong_partial', 'medium', 'low');
            }
            for ($i = 0; $i <= count($tokens) - 3; $i++) {
                $add(implode(' ', array_slice($tokens, $i, 3)), 'strong_partial', 'medium', 'low');
            }
        }
        foreach ($tokens as $token) {
            if (mb_strlen($token) >= 2 && preg_match('/^(de|si|sau|cu|la|in|în|pentru|despre)$/iu', $token) !== 1) {
                $add($token, 'conservative_single', 'low', 'medium');
            }
        }
        return array_values($candidates);
    }

    private function aysa_adjustments_internal_linking_find_safe_anchor_candidate_snippet($content, $candidates) {
        foreach (is_array($candidates) ? $candidates : array() as $candidate) {
            if (!is_array($candidate)) {
                continue;
            }
            $anchor = sanitize_text_field((string) ($candidate['anchor_text'] ?? ''));
            if ($anchor === '') {
                continue;
            }
            $snippet = $this->aysa_adjustments_internal_linking_find_safe_anchor_snippet($content, $anchor);
            if (!empty($snippet['ok'])) {
                $snippet['match_type'] = sanitize_key((string) ($candidate['match_type'] ?? 'strong_partial'));
                $snippet['confidence'] = sanitize_key((string) ($candidate['confidence'] ?? 'medium'));
                $snippet['risk'] = sanitize_key((string) ($candidate['risk'] ?? 'low'));
                return $snippet;
            }
        }
        return array('ok' => false, 'reason' => 'safe_anchor_paragraph_not_found');
    }

    private function aysa_adjustments_internal_linking_contextual_append_snippet($content, $anchor, $target_url) {
        $content = (string) $content;
        $anchor = trim(sanitize_text_field((string) $anchor));
        $target_url = esc_url_raw((string) $target_url);
        if ($content === '' || $anchor === '' || $target_url === '') {
            return array('ok' => false, 'reason' => 'blocked_no_safe_anchor_or_append');
        }
        if (function_exists('has_blocks') && has_blocks($content)) {
            return array('ok' => false, 'reason' => 'blocked_uneditable_source');
        }
        if (preg_match('/\[[a-z0-9_\-]+(?:\s|\])/i', $content) === 1 || stripos($content, '<!-- wp:shortcode') !== false) {
            return array('ok' => false, 'reason' => 'blocked_html_risk');
        }
        if (preg_match('/<(script|style|iframe|form|nav|footer|aside|table|button)\b/i', $content) === 1) {
            return array('ok' => false, 'reason' => 'blocked_html_risk');
        }

        $current_snippet = '';
        if (preg_match_all('/<p\b[^>]*>.*?<\/p>/is', $content, $matches) === 1 && !empty($matches[0])) {
            $paragraphs = array_reverse((array) $matches[0]);
            foreach ($paragraphs as $paragraph) {
                $paragraph = (string) $paragraph;
                if (preg_match('/<(a|h[1-6]|button|nav|footer|aside|table|figure|figcaption)\b/i', $paragraph) === 1) {
                    continue;
                }
                if (substr_count($content, $paragraph) === 1 && trim(wp_strip_all_tags($paragraph)) !== '') {
                    $current_snippet = $paragraph;
                    break;
                }
            }
        }
        if ($current_snippet === '') {
            $chunks = preg_split('/\n{2,}/', trim($content)) ?: array();
            $chunks = array_reverse(array_values(array_filter(array_map('trim', $chunks))));
            foreach ($chunks as $chunk) {
                if ($chunk !== '' && substr_count($content, $chunk) === 1 && preg_match('/<[^>]+>/', $chunk) !== 1) {
                    $current_snippet = $chunk;
                    break;
                }
            }
        }
        if ($current_snippet === '') {
            return array('ok' => false, 'reason' => 'blocked_no_safe_anchor_or_append');
        }

        $linked_anchor = '<a href="' . esc_url($target_url) . '" data-aysa-internal-link="1">' . esc_html($anchor) . '</a>';
        $sentence = 'Pentru o alegere spectaculoasă, vezi și colecția noastră de ' . $linked_anchor . '.';
        $append_paragraph = '<p>' . $sentence . '</p>';
        $proposed_snippet = $current_snippet . "\n\n" . $append_paragraph;
        return array(
            'ok' => true,
            'current_html_snippet' => $current_snippet,
            'proposed_html_snippet' => $proposed_snippet,
            'matched_anchor' => $anchor,
            'match_type' => 'contextual_append',
            'insertion_method' => 'append_contextual_sentence',
            'source_sentence' => wp_strip_all_tags($sentence),
            'confidence' => 'high',
            'risk' => 'low',
        );
    }

    private function aysa_adjustments_internal_linking_find_safe_anchor_snippet($content, $anchor) {
        $content = (string) $content;
        $anchor = trim((string) $anchor);
        if ($content === '' || $anchor === '') {
            return array('ok' => false, 'reason' => 'anchor_context_missing');
        }
        if (preg_match_all('/<p\b[^>]*>.*?<\/p>/is', $content, $matches) !== 1 || empty($matches[0])) {
            return array('ok' => false, 'reason' => 'paragraph_context_missing');
        }
        foreach ((array) $matches[0] as $paragraph) {
            $paragraph = (string) $paragraph;
            if (stripos(wp_strip_all_tags($paragraph), $anchor) === false) {
                continue;
            }
            if (preg_match('/<(?!(\/?p\b))[^>]+>/i', $paragraph) === 1) {
                continue;
            }
            if (preg_match('/<(a|h[1-6]|button|nav|footer|aside|table|figure|figcaption)\b/i', $paragraph) === 1) {
                continue;
            }
            if (preg_match('/\[[a-z0-9_\-]+(?:\s|\])/i', $paragraph) === 1) {
                continue;
            }
            if (substr_count($content, $paragraph) !== 1) {
                continue;
            }
            if (preg_match('/' . preg_quote($anchor, '/') . '/iu', $paragraph, $match) !== 1) {
                continue;
            }
            $matched_anchor = (string) ($match[0] ?? $anchor);
            if ($matched_anchor === '' || substr_count($paragraph, $matched_anchor) !== 1) {
                continue;
            }
            return array(
                'ok' => true,
                'current_html_snippet' => $paragraph,
                'matched_anchor' => $matched_anchor,
                'match_type' => 'exact',
                'insertion_method' => 'link_existing_phrase',
                'source_sentence' => sanitize_text_field(wp_strip_all_tags($paragraph)),
                'confidence' => 'high',
                'risk' => 'low',
            );
        }
        return array('ok' => false, 'reason' => 'safe_anchor_paragraph_not_found');
    }

    private function aysa_adjustments_get_technical_sitemap_robots_snapshot($project_id) {
        $project_id = intval($project_id);
        $state = get_option($this->aysa_technical_sitemap_robots_state_key($project_id), array());
        if (!is_array($state)) {
            $state = array();
        }
        $state_status = sanitize_key((string) ($state['status'] ?? 'idle'));

        $data = !empty($state['data']) && is_array($state['data']) ? $state['data'] : array();
        $summary = !empty($data['summary']) && is_array($data['summary']) ? $data['summary'] : array();
        $checks = !empty($data['checks']) && is_array($data['checks']) ? array_values($data['checks']) : array();
        $sitemaps = !empty($data['sitemaps']) && is_array($data['sitemaps']) ? array_values($data['sitemaps']) : array();
        $robots = !empty($data['robots']) && is_array($data['robots']) ? $data['robots'] : array();
        $generation = !empty($data['generation']) && is_array($data['generation']) ? $data['generation'] : array();

        $overview_rows = array(
            array('metric' => 'Errors', 'value' => (string) intval($summary['errors'] ?? 0)),
            array('metric' => 'Warnings', 'value' => (string) intval($summary['warnings'] ?? 0)),
            array('metric' => 'Passes', 'value' => (string) intval($summary['passes'] ?? 0)),
            array('metric' => 'Robots health', 'value' => sanitize_text_field((string) ($summary['robots_health'] ?? ''))),
            array('metric' => 'Sitemap health', 'value' => sanitize_text_field((string) ($summary['sitemap_health'] ?? ''))),
            array('metric' => 'Primary sitemap', 'value' => esc_url_raw((string) ($data['primary_sitemap'] ?? ''))),
        );

        $robots_rows = array(
            array('field' => 'Source', 'value' => sanitize_text_field((string) ($robots['source_label'] ?? ''))),
            array('field' => 'Target URL', 'value' => esc_url_raw((string) ($data['target_url'] ?? ''))),
            array('field' => 'Robots content', 'value' => sanitize_textarea_field((string) ($robots['content'] ?? ''))),
        );

        $has_data = !empty($checks) || !empty($sitemaps) || !empty($robots['content']);
        $snapshot_status = $has_data ? 'ready' : (in_array($state_status, array('queued', 'running', 'completed', 'failed', 'blocked'), true) ? $state_status : 'idle');

        return array(
            'id' => 'technical_sitemap_robots',
            'status' => $snapshot_status,
            'updated_at' => sanitize_text_field((string) ($state['updated_at'] ?? '')),
            'target_url' => esc_url_raw((string) ($data['target_url'] ?? '')),
            'primary_sitemap' => esc_url_raw((string) ($data['primary_sitemap'] ?? '')),
            'errors' => intval($summary['errors'] ?? 0),
            'warnings' => intval($summary['warnings'] ?? 0),
            'passes' => intval($summary['passes'] ?? 0),
            'robots_health' => sanitize_text_field((string) ($summary['robots_health'] ?? '')),
            'sitemap_health' => sanitize_text_field((string) ($summary['sitemap_health'] ?? '')),
            'overview_rows' => array_values($overview_rows),
            'rows' => array_values($checks),
            'sitemaps' => array_values($sitemaps),
            'robots_rows' => array_values($robots_rows),
            'generation' => $generation,
            'count' => count($checks),
            'has_data' => $has_data,
            'run_id' => intval($state['run_id'] ?? 0),
        );
    }

    private function aysa_agent8v3_hydrate_sitemap_robots_public_readback_snapshot($snapshot, $project_id, $target_url) {
        $snapshot = is_array($snapshot) ? $snapshot : array();
        $project_id = intval($project_id);
        $target_url = untrailingslashit((string) $target_url);
        if ($target_url === '') {
            $target_url = untrailingslashit(home_url('/'));
        }

        $has_public_readback = !empty($snapshot['public_readback']) && is_array($snapshot['public_readback']);
        $has_sitemap_snapshot = !empty($snapshot['primary_sitemap']) || !empty($snapshot['sitemaps']);
        $has_robots_snapshot = !empty($snapshot['robots_rows'][2]['value']);
        if ($has_public_readback && $has_sitemap_snapshot && $has_robots_snapshot) {
            return $snapshot;
        }

        $robots_path = $this->aysa_technical_robots_file_path();
        $sitemap_path = $this->aysa_technical_sitemap_file_path();
        $generation_state = get_option($this->aysa_technical_sitemap_generation_state_key(), array());
        $cache_fingerprint = array(
            'project_id' => $project_id,
            'target_url' => $target_url,
            'robots_mtime' => file_exists($robots_path) ? intval(filemtime($robots_path)) : 0,
            'robots_size' => file_exists($robots_path) ? intval(filesize($robots_path)) : 0,
            'sitemap_mtime' => file_exists($sitemap_path) ? intval(filemtime($sitemap_path)) : 0,
            'sitemap_size' => file_exists($sitemap_path) ? intval(filesize($sitemap_path)) : 0,
            'generation_hash' => hash('sha256', wp_json_encode(is_array($generation_state) ? $generation_state : array())),
            'blog_public' => (string) get_option('blog_public', '1'),
        );
        $cache_key = 'aysa_tsr_readback_' . substr(hash('sha256', wp_json_encode($cache_fingerprint)), 0, 32);
        $cached_snapshot = get_transient($cache_key);
        if (is_array($cached_snapshot) && !empty($cached_snapshot['public_readback'])) {
            return array_replace($snapshot, $cached_snapshot);
        }

        $audit = $this->aysa_technical_build_sitemap_robots_audit($target_url, $project_id);
        if (!is_array($audit) || $audit === array()) {
            return $snapshot;
        }

        $active_takeover = !empty($audit['sitemap_authority_active_takeover'])
            && is_array($audit['sitemap_authority_active_takeover'])
            && !empty($audit['sitemap_authority_active_takeover']['active']);
        $physical_sitemap_present = file_exists($this->aysa_technical_sitemap_file_path());
        $sitemaps = !empty($audit['sitemaps']) && is_array($audit['sitemaps']) ? array_values($audit['sitemaps']) : array();
        if (!$active_takeover) {
            foreach ($sitemaps as $idx => $sitemap_row) {
                if (!is_array($sitemap_row)) {
                    continue;
                }
                if ((string) ($sitemap_row['provider'] ?? '') === 'aysa') {
                    $sitemaps[$idx]['provider'] = $physical_sitemap_present ? 'physical_sitemap' : 'public_sitemap';
                    $sitemaps[$idx]['provider_label'] = $physical_sitemap_present
                        ? 'Physical sitemap file (ownership unverified)'
                        : 'Public sitemap endpoint';
                }
            }
        }

        $robots = !empty($audit['robots']) && is_array($audit['robots']) ? $audit['robots'] : array();
        $robots_url = $target_url . '/robots.txt';
        $robots_status = 0;
        $robots_content_type = '';
        $robots_body = '';
        $robots_resp = wp_remote_get($robots_url, array(
            'timeout' => 8,
            'redirection' => 2,
            'limit_response_size' => 120000,
            'headers' => array('Accept' => 'text/plain,*/*'),
        ));
        if (!is_wp_error($robots_resp)) {
            $robots_status = intval(wp_remote_retrieve_response_code($robots_resp));
            $robots_content_type = sanitize_text_field((string) wp_remote_retrieve_header($robots_resp, 'content-type'));
            $robots_body = (string) wp_remote_retrieve_body($robots_resp);
        }
        if ($robots_body === '' && !empty($robots['content'])) {
            $robots_body = (string) $robots['content'];
        }

        $robots_sitemap_directives = array();
        if ($robots_body !== '' && preg_match_all('/^\s*sitemap:\s*(https?:\/\/\S+)\s*$/im', $robots_body, $matches)) {
            $robots_sitemap_directives = array_values(array_unique(array_map('esc_url_raw', (array) ($matches[1] ?? array()))));
        }

        $primary_sitemap = esc_url_raw((string) ($audit['primary_sitemap'] ?? ($snapshot['primary_sitemap'] ?? '')));
        $primary_sitemap_row = array();
        foreach ($sitemaps as $sitemap_row) {
            if (!is_array($sitemap_row)) {
                continue;
            }
            $row_url = esc_url_raw((string) ($sitemap_row['url'] ?? $sitemap_row['loc'] ?? ''));
            if ($primary_sitemap !== '' && $row_url === $primary_sitemap) {
                $primary_sitemap_row = $sitemap_row;
                break;
            }
            if ($primary_sitemap_row === array() && !empty($sitemap_row['reachable'])) {
                $primary_sitemap_row = $sitemap_row;
            }
        }

        $inventory = !empty($audit['current_provider_inventory']) && is_array($audit['current_provider_inventory'])
            ? $audit['current_provider_inventory']
            : array();
        $sitemap_http_status = intval($primary_sitemap_row['status_code'] ?? $primary_sitemap_row['http_status'] ?? 0);
        $sitemap_detected = $primary_sitemap !== '' && ($sitemap_http_status >= 200 && $sitemap_http_status < 400 || !empty($primary_sitemap_row['reachable']));
        $child_sitemap_urls = !empty($inventory['child_sitemap_urls']) && is_array($inventory['child_sitemap_urls'])
            ? array_values($inventory['child_sitemap_urls'])
            : array();
        $child_sitemap_count = intval($inventory['child_sitemap_count'] ?? count($child_sitemap_urls));
        $reachable_child_sitemap_count = intval($inventory['reachable_child_sitemap_count'] ?? 0);
        $unreachable_child_sitemap_count = intval($inventory['unreachable_child_sitemap_count'] ?? 0);
        $invalid_child_sitemap_count = intval($inventory['invalid_child_sitemap_count'] ?? 0);
        $sitemap_children_healthy = !empty($inventory['children_healthy']);
        if ($child_sitemap_count <= 0 && isset($primary_sitemap_row['entries_count']) && is_numeric($primary_sitemap_row['entries_count'])) {
            $child_sitemap_count = intval($primary_sitemap_row['entries_count']);
        }

        $summary = !empty($audit['summary']) && is_array($audit['summary']) ? $audit['summary'] : array();
        $snapshot['id'] = 'technical_sitemap_robots';
        $snapshot['status'] = 'ready';
        $snapshot['updated_at'] = sanitize_text_field((string) ($audit['generated_at'] ?? ($snapshot['updated_at'] ?? '')));
        $snapshot['target_url'] = esc_url_raw((string) ($audit['target_url'] ?? $target_url));
        $snapshot['primary_sitemap'] = $primary_sitemap;
        $snapshot['errors'] = intval($summary['errors'] ?? ($snapshot['errors'] ?? 0));
        $snapshot['warnings'] = intval($summary['warnings'] ?? ($snapshot['warnings'] ?? 0));
        $snapshot['passes'] = intval($summary['passes'] ?? ($snapshot['passes'] ?? 0));
        $snapshot['robots_health'] = sanitize_text_field((string) ($summary['robots_health'] ?? ($snapshot['robots_health'] ?? '')));
        $snapshot['sitemap_health'] = sanitize_text_field((string) ($summary['sitemap_health'] ?? ($snapshot['sitemap_health'] ?? '')));
        $snapshot['rows'] = !empty($audit['checks']) && is_array($audit['checks']) ? array_values($audit['checks']) : (is_array($snapshot['rows'] ?? null) ? $snapshot['rows'] : array());
        $snapshot['sitemaps'] = $sitemaps;
        $snapshot['robots_rows'] = array(
            array('field' => 'Source', 'value' => sanitize_text_field((string) ($robots['source_label'] ?? ($robots_status > 0 ? 'Public robots.txt' : '')))),
            array('field' => 'Target URL', 'value' => esc_url_raw((string) ($audit['target_url'] ?? $target_url))),
            array('field' => 'Robots content', 'value' => sanitize_textarea_field($robots_body)),
        );
        $snapshot['generation'] = !empty($audit['generation']) && is_array($audit['generation']) ? $audit['generation'] : (is_array($snapshot['generation'] ?? null) ? $snapshot['generation'] : array());
        $snapshot['providers'] = !$active_takeover
            ? array($physical_sitemap_present ? 'Physical sitemap file (ownership unverified)' : 'Public sitemap')
            : (is_array($audit['providers'] ?? null) ? $audit['providers'] : array());
        $snapshot['current_provider_inventory'] = $inventory;
        $snapshot['aysa_candidate_inventory'] = !empty($audit['aysa_candidate_inventory']) && is_array($audit['aysa_candidate_inventory'])
            ? $audit['aysa_candidate_inventory']
            : array();
        $snapshot['sitemap_parity_diff'] = !empty($audit['sitemap_parity_diff']) && is_array($audit['sitemap_parity_diff']) ? $audit['sitemap_parity_diff'] : array();
        $snapshot['sitemap_authority_dry_run_bridge'] = !empty($audit['sitemap_authority_dry_run_bridge']) && is_array($audit['sitemap_authority_dry_run_bridge']) ? $audit['sitemap_authority_dry_run_bridge'] : array();
        if (!empty($audit['sitemap_authority_active_takeover']) && is_array($audit['sitemap_authority_active_takeover'])) {
            $snapshot['sitemap_authority_active_takeover'] = $audit['sitemap_authority_active_takeover'];
        }
        $snapshot['public_readback'] = array(
            'source' => 'existing_sitemap_robots_public_readback',
            'writes_enabled' => false,
            'billing_enabled' => false,
            'sitemap_public_detected' => $sitemap_detected,
            'sitemap_public_url' => $primary_sitemap,
            'sitemap_http_status' => $sitemap_http_status,
            'sitemap_content_type' => sanitize_text_field((string) ($primary_sitemap_row['content_type'] ?? 'application/xml')),
            'child_sitemap_count' => $child_sitemap_count,
            'child_sitemap_urls' => array_slice(array_map('esc_url_raw', $child_sitemap_urls), 0, 10),
            'reachable_child_sitemap_count' => $reachable_child_sitemap_count,
            'unreachable_child_sitemap_count' => $unreachable_child_sitemap_count,
            'invalid_child_sitemap_count' => $invalid_child_sitemap_count,
            'sitemap_children_healthy' => $sitemap_children_healthy,
            'sitemap_url_count' => intval($inventory['url_count'] ?? 0),
            'robots_public_detected' => $robots_status >= 200 && $robots_status < 400,
            'robots_url' => esc_url_raw($robots_url),
            'robots_http_status' => $robots_status,
            'robots_content_type' => $robots_content_type,
            'robots_sitemap_directive' => isset($robots_sitemap_directives[0]) ? $robots_sitemap_directives[0] : '',
            'robots_sitemap_directives' => array_slice($robots_sitemap_directives, 0, 10),
            'provider_label' => $active_takeover ? 'AYSA' : 'Public sitemap',
            'sitemap_source' => $physical_sitemap_present ? 'physical_file' : 'public_endpoint',
            'sitemap_source_ownership' => $active_takeover ? 'aysa_owned' : 'unverified',
            'robots_source' => sanitize_key((string) ($robots['source'] ?? 'unknown')),
            'robots_source_ownership' => $active_takeover ? 'aysa_owned' : 'unverified',
            'aysa_generation_active' => $active_takeover,
        );
        $snapshot['count'] = count(is_array($snapshot['rows'] ?? null) ? $snapshot['rows'] : array());
        $snapshot['has_data'] = true;

        // This short-lived review cache prevents each tab from repeating the
        // same bounded public HTTP readback. It never changes sitemap/robots
        // output, does not call a provider, and cannot enable billing or apply.
        set_transient($cache_key, $snapshot, 2 * MINUTE_IN_SECONDS);

        return $snapshot;
    }

    private function aysa_adjustments_fetch_live_gsc_snapshot($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array('rows' => array());
        }

        $site_host = wp_parse_url(home_url(), PHP_URL_HOST);
        $site_host = is_string($site_host) ? $site_host : '';

        $result = $this->aysa_broker_request('POST', '/v1/google/keyword-mappings', array(
            'project_id' => $project_id,
            'site_host' => $site_host,
            'days' => 90,
            'limit' => 1000,
        ), 45);
        if (is_wp_error($result) || !is_array($result) || empty($result['connected'])) {
            return array('rows' => array());
        }

        $query_rows = isset($result['gsc_queries']) && is_array($result['gsc_queries']) ? $result['gsc_queries'] : array();
        if ($query_rows === array()) {
            return array('rows' => array());
        }

        $query_map = array();
        foreach ($query_rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keyword = trim((string) ($row['query'] ?? $row['keyword'] ?? ''));
            if ($keyword === '') {
                continue;
            }
            $norm = strtolower($keyword);
            $clicks = intval($row['clicks'] ?? 0);
            $impressions = intval($row['impressions'] ?? 0);
            if (!isset($query_map[$norm]) || $clicks > intval($query_map[$norm]['traffic'] ?? 0) || $impressions > intval($query_map[$norm]['impressions'] ?? 0)) {
                $query_map[$norm] = array(
                    'keyword' => $keyword,
                    'search_volume' => null,
                    'difficulty' => null,
                    'position' => isset($row['position']) ? round(floatval($row['position']), 2) : null,
                    'traffic' => $clicks,
                    'target_url' => '',
                    'source' => 'gsc_query',
                    'updated_at' => gmdate('Y-m-d H:i:s'),
                    'impressions' => $impressions,
                );
            }
        }

        return array(
            'rows' => array_slice(array_values($query_map), 0, 8),
        );
    }

    private function aysa_adjustments_run_keyword_research_sync($project_id, $setup_user_id = 0) {
        $snapshot = $this->aysa_adjustments_get_keyword_research_snapshot($project_id, $setup_user_id);
        $gsc_count = intval($snapshot['gsc_count'] ?? 0);
        $suggestions_count = intval($snapshot['suggestions_count'] ?? 0);
        $status = isset($snapshot['refresh_status']) && is_array($snapshot['refresh_status']) ? $snapshot['refresh_status'] : array();

        if ($gsc_count <= 0 && (($status['status'] ?? 'idle') !== 'running')) {
            $run_id = uniqid('kwd_', true);
            $now = time();
            $this->aysa_keyword_discovery_save_status($project_id, array(
                'run_id' => $run_id,
                'status' => 'running',
                'total' => 0,
                'processed' => 0,
                'inserted' => 0,
                'updated' => 0,
                'staged' => 0,
                'apply_to_wp' => 1,
                'last_error' => '',
                'started_at' => $now,
                'updated_at' => $now,
                'finished_at' => 0,
            ));
            $this->aysa_keyword_discovery_schedule_background_refresh($project_id, 1);
        }

        if ($suggestions_count <= 0) {
            $this->aysa_keyword_discovery_refresh_keyword_suggestions($project_id, $setup_user_id, false);
        }

        return $this->aysa_adjustments_get_keyword_research_snapshot($project_id, $setup_user_id);
    }

    private function aysa_adjustments_run_competitor_gap_sync($project_id, $setup_user_id = 0) {
        $seed_data = $this->aysa_keyword_strategy_extract_seed_data($setup_user_id > 0 ? $setup_user_id : get_current_user_id());
        $competitors = isset($seed_data['competitors']) && is_array($seed_data['competitors']) ? array_values($seed_data['competitors']) : array();
        $target = (string) wp_parse_url(home_url('/'), PHP_URL_HOST);
        $context = $this->aysa_monitoring_resolve_context($target);
        $payload = array(
            'project_id' => intval($project_id),
            'target' => trim((string) ($context['target'] ?? $target)),
            'location_code' => intval($context['location_code'] ?? 2840),
            'language_code' => (string) ($context['language_code'] ?? 'ro'),
            'limit' => 200,
            'competitors' => array_values(array_filter(array_map(function($competitor) {
                return $this->aysa_monitoring_extract_host((string) $competitor);
            }, $competitors))),
        );
        $result = $this->aysa_broker_request('POST', '/v1/keywords/gap', $payload, 180);
        if (!is_wp_error($result)) {
            $data = isset($result['data']) && is_array($result['data']) ? $result['data'] : $result;
            if (is_array($data) && isset($data['rankedKeywords'])) {
                $this->aysa_competitor_gap_save_cached_payload($project_id, $payload, $data);
            }
        }
        return $this->aysa_adjustments_get_competitor_gap_snapshot($project_id);
    }

    private function aysa_adjustments_run_relevant_keywords_sync($project_id, $setup_user_id = 0, $language = 'en') {
        $project_id = intval($project_id);
        $setup_user_id = intval($setup_user_id);
        $language = in_array($language, array('en', 'ro', 'bg'), true) ? $language : 'en';

        $gap_snapshot = $this->aysa_adjustments_get_competitor_gap_snapshot($project_id);
        $gap_started_now = empty($gap_snapshot['has_data']);
        if ($gap_started_now) {
            $gap_snapshot = $this->aysa_adjustments_run_competitor_gap_sync($project_id, $setup_user_id);
        }

        if (empty($gap_snapshot['has_data'])) {
            return array(
                'module' => 'competitor_gap',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I started from competitor gap, but I still do not have saved gap rows to filter yet. The next good step is to make sure we have competitors and then rerun the gap.',
                    'Am început cu competitor gap, dar încă nu am rânduri salvate pe care să le filtrez. Următorul pas bun este să ne asigurăm că avem competitori și apoi să rulăm din nou gap-ul.',
                    'Започнах с competitor gap, но все още нямам запазени редове, които да филтрирам. Следващата добра стъпка е да се уверим, че имаме конкуренти, и после да пуснем gap-а отново.'
                ),
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'Show competitors', 'Arată competitorii', 'Покажи конкурентите'),
                        'message' => $this->aysa_adjustments_tr($language, 'Show me the competitors found so far.', 'Arată-mi competitorii găsiți până acum.', 'Покажи ми намерените конкуренти дотук.'),
                        'ui_action' => 'open_artifact_competitor_identification',
                    ),
                ),
            );
        }

        $keyword_snapshot = $this->aysa_adjustments_get_keyword_research_snapshot($project_id, $setup_user_id);
        $selection = $this->aysa_adjustments_select_relevant_keywords_from_all_sources(
            $project_id,
            $setup_user_id,
            $keyword_snapshot,
            $gap_snapshot
        );
        $selected_rows = isset($selection['rows']) && is_array($selection['rows']) ? array_values($selection['rows']) : array();
        if (empty($selected_rows)) {
            return array(
                'module' => 'keyword_research',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I checked the keywords already available from Google, suggestions, and competitor gap, but I did not find a clean enough set to add automatically without noise. I prefer to stay conservative than save the wrong terms.',
                    'Am verificat keyword-urile deja disponibile din Google, suggestions și competitor gap, dar nu am găsit un set suficient de curat ca să îl adaug automat fără zgomot. Prefer să rămân conservator decât să salvez termenii greșiți.',
                    'Проверих keyword-ите, които вече са налични от Google, suggestions и competitor gap, но не намерих достатъчно чист набор, който да добавя автоматично без шум. Предпочитам да остана по-консервативен, вместо да запазя грешни термини.'
                ),
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See unified keywords', 'Vezi keyword-urile unificate', 'Виж обединените keyword-и'),
                        'message' => $this->aysa_adjustments_tr($language, 'Open the unified keyword view and show me the current sources.', 'Deschide vederea unificată și arată-mi sursele curente.', 'Отвори обединения keyword изглед и ми покажи текущите източници.'),
                        'ui_action' => 'open_artifact_unified_keywords',
                    ),
                ),
                'ui_action' => 'open_artifact_unified_keywords',
            );
        }

        $existing_rows = $this->aysa_adjustments_get_local_keywords_by_sources(
            $project_id,
            array('gsc', 'gsc_query', 'gsc_query_only', 'keyword_suggestion', 'related_keyword', 'competitor_gap')
        );
        $existing_lookup = array();
        foreach ($existing_rows as $existing_row) {
            if (!is_array($existing_row)) {
                continue;
            }
            $keyword = trim((string) ($existing_row['keyword'] ?? ''));
            if ($keyword === '') {
                continue;
            }
            $existing_lookup[$this->aysa_adjustments_normalize_keyword_lookup_key($keyword)] = true;
        }

        $persist_rows = array();
        foreach ($selected_rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $source_keys = isset($row['source_keys']) && is_array($row['source_keys']) ? $row['source_keys'] : array();
            $keyword = (string) ($row['keyword'] ?? '');
            $keyword_norm = $this->aysa_adjustments_normalize_keyword_lookup_key($keyword);
            $already_exists = !empty($existing_lookup[$keyword_norm]);
            $needs_insert = !$already_exists && !empty($source_keys['competitor_gap']);
            if (!$needs_insert) {
                continue;
            }
            $persist_rows[] = array(
                'keyword' => $keyword,
                'searchVolume' => isset($row['search_volume']) ? intval($row['search_volume']) : null,
                'difficulty' => isset($row['difficulty']) ? floatval($row['difficulty']) : null,
                'intent' => (string) ($row['intent'] ?? ''),
                'competition' => '',
                'traffic' => null,
                'trafficPct' => null,
                'position' => null,
                'cpc' => null,
                'url' => '',
                'item_types' => array(),
                'source' => 'competitor_gap',
            );
        }

        $persist_result = $this->aysa_keyword_discovery_persist_keywords($project_id, $persist_rows);
        $association_result = $this->aysa_adjustments_auto_associate_selected_keywords(
            $project_id,
            isset($selection['matched_items']) && is_array($selection['matched_items']) ? $selection['matched_items'] : array()
        );

        $selected_count = count($persist_rows);
        $associated_count = intval($association_result['associated'] ?? 0);
        $candidate_count = intval($association_result['matched_candidates'] ?? 0);
        $selection_count = count($selected_rows);

        $this->aysa_adjustments_save_recent_relevant_keywords($project_id, array(
            'selected_keywords' => array_values(array_map(function($row) {
                return (string) ($row['keyword'] ?? '');
            }, $selected_rows)),
            'updated_at' => current_time('mysql'),
        ));

        $message = $gap_started_now
            ? $this->aysa_adjustments_tr(
                $language,
                sprintf(
                    'I first started competitor gap because it was missing. Then I checked Google visibility, keyword suggestions, and gap opportunities together, kept %d relevant keywords for your business, added %d new gap terms into the keyword map, found direct page matches for %d of them, and created %d new saved associations.',
                    $selection_count,
                    $selected_count,
                    $candidate_count,
                    $associated_count
                ),
                sprintf(
                    'Am pornit mai întâi competitor gap-ul, pentru că lipsea. Apoi am verificat împreună vizibilitatea din Google, keyword suggestions și oportunitățile din gap, am păstrat %d keyword-uri relevante pentru afacerea ta, am adăugat %d termeni noi din gap în keyword map, am găsit potriviri directe cu pagini pentru %d dintre ele și am creat %d asocieri noi salvate.',
                    $selection_count,
                    $selected_count,
                    $candidate_count,
                    $associated_count
                ),
                sprintf(
                    'Първо стартирах competitor gap, защото липсваше. След това проверих заедно Google видимостта, keyword suggestions и gap възможностите, запазих %d релевантни keyword-а за твоя бизнес, добавих %d нови gap термина в keyword map-а, намерих директни съвпадения със страници за %d от тях и създадох %d нови запазени свързвания.',
                    $selection_count,
                    $selected_count,
                    $candidate_count,
                    $associated_count
                )
            )
            : $this->aysa_adjustments_tr(
                $language,
                sprintf(
                    'I checked the Google rows, the saved keyword suggestions, and the competitor gap together. I kept %d relevant keywords for your business, added %d new gap terms into the keyword map where needed, found direct page matches for %d of them, and created %d new saved associations.',
                    $selection_count,
                    $selected_count,
                    $candidate_count,
                    $associated_count
                ),
                sprintf(
                    'Am verificat împreună rândurile din Google, keyword suggestions și competitor gap. Am păstrat %d keyword-uri relevante pentru afacerea ta, am adăugat %d termeni noi din gap în keyword map unde a fost nevoie, am găsit potriviri directe cu pagini pentru %d dintre ele și am creat %d asocieri noi salvate.',
                    $selection_count,
                    $selected_count,
                    $candidate_count,
                    $associated_count
                ),
                sprintf(
                    'Проверих заедно Google редовете, keyword suggestions и competitor gap. Запазих %d релевантни keyword-а за твоя бизнес, добавих %d нови gap термина в keyword map-а при нужда, намерих директни съвпадения със страници за %d от тях и създадох %d нови запазени свързвания.',
                    $selection_count,
                    $selected_count,
                    $candidate_count,
                    $associated_count
                )
            );

        $suggested_actions = array(
            array(
                'label' => $this->aysa_adjustments_tr($language, 'See unified keywords', 'Vezi keyword-urile unificate', 'Виж обединените keyword-и'),
                'message' => $this->aysa_adjustments_tr($language, 'Show me the unified keywords and source markers.', 'Arată-mi keyword-urile unificate și marcajele de sursă.', 'Покажи ми обединените keyword-и и маркерите за източник.'),
                'ui_action' => 'open_artifact_unified_keywords',
            ),
        );
        if ($associated_count > 0) {
            $suggested_actions[] = array(
                'label' => $this->aysa_adjustments_tr($language, 'See matches', 'Vezi asocierile', 'Виж свързванията'),
                'message' => $this->aysa_adjustments_tr($language, 'Show me the keyword-to-page matches.', 'Arată-mi asocierile keyword-urilor cu paginile.', 'Покажи ми свързването между keyword-ите и страниците.'),
                'ui_action' => 'open_artifact_matches',
            );
        }

        return array(
            'module' => 'keyword_research',
            'assistant_message' => $message,
            'suggested_actions' => $suggested_actions,
            'ui_action' => '',
            'meta' => array(
                'selected_keywords' => $selection_count,
                'persisted_keywords' => intval($persist_result['total'] ?? 0),
                'associated_keywords' => $associated_count,
            ),
        );
    }

    private function aysa_adjustments_select_relevant_keywords_from_all_sources($project_id, $setup_user_id, $keyword_snapshot, $gap_snapshot) {
        $context = $this->aysa_adjustments_collect_business_relevance_context($setup_user_id, $project_id);
        $selected = array();
        $matched_items = array();
        $rows_by_keyword = array();

        $collect = function($rows, $source_key) use (&$rows_by_keyword) {
            foreach (is_array($rows) ? $rows : array() as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $keyword = trim((string) ($row['keyword'] ?? ''));
                if ($keyword === '') {
                    continue;
                }
                $norm = $this->aysa_adjustments_normalize_keyword_lookup_key($keyword);
                if ($norm === '') {
                    continue;
                }
                if (!isset($rows_by_keyword[$norm])) {
                    $rows_by_keyword[$norm] = array(
                        'keyword' => $keyword,
                        'search_volume' => 0,
                        'difficulty' => '',
                        'intent' => '',
                        'position' => '',
                        'source_keys' => array(),
                    );
                }
                $entry = $rows_by_keyword[$norm];
                $entry['source_keys'][$source_key] = true;
                $volume = intval($row['search_volume'] ?? $row['searchVolume'] ?? 0);
                if ($volume > intval($entry['search_volume'])) {
                    $entry['search_volume'] = $volume;
                }
                $difficulty = $row['difficulty'] ?? '';
                if ($entry['difficulty'] === '' && $difficulty !== '' && $difficulty !== 'N/A') {
                    $entry['difficulty'] = (string) $difficulty;
                }
                $intent = trim((string) ($row['intent'] ?? ''));
                if ($entry['intent'] === '' && $intent !== '' && $intent !== 'N/A') {
                    $entry['intent'] = $intent;
                }
                $position = $row['position'] ?? '';
                if ($entry['position'] === '' && $position !== '' && $position !== 'N/A') {
                    $entry['position'] = (string) $position;
                }
                $rows_by_keyword[$norm] = $entry;
            }
        };

        $collect(isset($keyword_snapshot['gsc_rows']) ? $keyword_snapshot['gsc_rows'] : array(), 'gsc');
        $collect(isset($keyword_snapshot['suggestion_rows']) ? $keyword_snapshot['suggestion_rows'] : array(), 'suggestions');
        $collect(isset($gap_snapshot['rows']) ? $gap_snapshot['rows'] : array(), 'competitor_gap');

        foreach ($rows_by_keyword as $norm => $row) {
            $keyword = trim((string) ($row['keyword'] ?? ''));
            if ($keyword === '') {
                continue;
            }
            $candidate = $this->aysa_adjustments_match_keyword_to_wp_item($keyword, $context['catalog_items']);
            if (!$this->aysa_adjustments_keyword_passes_business_gate($keyword, $row, $context, $candidate)) {
                continue;
            }
            $score = $this->aysa_adjustments_score_keyword_relevance($keyword, $row, $context, $candidate);
            if (!empty($row['source_keys']['gsc'])) {
                $score += 0.12;
            }
            if (!empty($row['source_keys']['suggestions'])) {
                $score += 0.06;
            }
            if (!empty($row['source_keys']['competitor_gap'])) {
                $score += 0.05;
            }
            $score = min(1, $score);
            if ($score < 0.42) {
                continue;
            }

            $row['relevance_score'] = $score;
            $selected[] = $row;
            if (is_array($candidate) && !empty($candidate['id'])) {
                $candidate['keyword'] = $keyword;
                $matched_items[$norm] = $candidate;
            }
        }

        usort($selected, function($left, $right) {
            $left_score = floatval($left['relevance_score'] ?? 0);
            $right_score = floatval($right['relevance_score'] ?? 0);
            if ($left_score === $right_score) {
                return intval($right['search_volume'] ?? 0) <=> intval($left['search_volume'] ?? 0);
            }
            return $right_score <=> $left_score;
        });

        return array(
            'rows' => $selected,
            'matched_items' => $matched_items,
        );
    }

    private function aysa_adjustments_select_relevant_keywords_from_competitor_gap($project_id, $setup_user_id, $gap_snapshot) {
        $context = $this->aysa_adjustments_collect_business_relevance_context($setup_user_id, $project_id);
        $rows = isset($gap_snapshot['rows']) && is_array($gap_snapshot['rows']) ? array_values($gap_snapshot['rows']) : array();
        $selected = array();
        $matched_items = array();

        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keyword = trim((string) ($row['keyword'] ?? ''));
            if ($keyword === '') {
                continue;
            }

            $candidate = $this->aysa_adjustments_match_keyword_to_wp_item($keyword, $context['catalog_items']);
            if (!$this->aysa_adjustments_keyword_passes_business_gate($keyword, $row, $context, $candidate)) {
                continue;
            }
            $score = $this->aysa_adjustments_score_keyword_relevance($keyword, $row, $context, $candidate);
            if ($score < 0.42) {
                continue;
            }

            $selected[] = array_merge($row, array(
                'relevance_score' => $score,
            ));

            if (is_array($candidate) && !empty($candidate['id'])) {
                $candidate['keyword'] = $keyword;
                $matched_items[strtolower(trim($keyword))] = $candidate;
            }
        }

        usort($selected, function($left, $right) {
            $left_score = floatval($left['relevance_score'] ?? 0);
            $right_score = floatval($right['relevance_score'] ?? 0);
            if ($left_score === $right_score) {
                return intval($right['search_volume'] ?? 0) <=> intval($left['search_volume'] ?? 0);
            }
            return $right_score <=> $left_score;
        });

        return array(
            'rows' => $selected,
            'matched_items' => $matched_items,
        );
    }

    private function aysa_adjustments_collect_business_relevance_context($setup_user_id, $project_id = 0) {
        $setup_user_id = intval($setup_user_id);
        $project_id = intval($project_id);
        if ($setup_user_id <= 0) {
            $setup_user_id = intval($this->aysa_get_authenticated_external_user_id());
            if ($setup_user_id <= 0) {
                $setup_user_id = get_current_user_id();
            }
        }

        $seed_data = $this->aysa_keyword_strategy_extract_seed_data($setup_user_id);
        $general = get_option('aysa_wizard_general_' . intval($setup_user_id), array());
        if (!is_array($general)) {
            $general = array();
        }
        $context = $this->aysa_keyword_strategy_get_context($setup_user_id);

        $text_parts = array_filter(array(
            (string) ($general['company_name'] ?? ''),
            (string) ($general['brand'] ?? ''),
            (string) ($general['company_description'] ?? ''),
            (string) ($general['business_information'] ?? ''),
            (string) ($general['industry'] ?? ''),
            (string) ($general['website_type'] ?? ''),
            implode(' ', isset($seed_data['seed_keywords']) && is_array($seed_data['seed_keywords']) ? $seed_data['seed_keywords'] : array()),
        ));
        $business_tokens = $this->aysa_adjustments_tokenize_keyword_text(implode(' ', $text_parts));
        $industry_hints = $this->aysa_adjustments_get_industry_keyword_hints((string) ($context['industry'] ?? ''), (string) ($context['website_type'] ?? ''));
        $hint_tokens = $this->aysa_adjustments_tokenize_keyword_text(implode(' ', $industry_hints));
        $own_brand_tokens = $this->aysa_adjustments_extract_brand_tokens(array(
            (string) ($general['company_name'] ?? ''),
            (string) ($general['brand'] ?? ''),
            (string) ($general['website'] ?? ''),
            (string) home_url('/'),
        ));
        $competitor_brand_tokens = $project_id > 0 ? $this->aysa_adjustments_get_competitor_brand_tokens($project_id, array_values(array_unique(array_merge($business_tokens, $hint_tokens, $own_brand_tokens)))) : array();
        $catalog_items = $this->aysa_adjustments_get_wp_catalog_items();
        $catalog_items = $this->aysa_adjustments_filter_catalog_items_for_business($catalog_items, array_values(array_unique(array_merge($business_tokens, $hint_tokens))));

        return array(
            'seed_keywords' => isset($seed_data['seed_keywords']) && is_array($seed_data['seed_keywords']) ? array_values($seed_data['seed_keywords']) : array(),
            'business_tokens' => array_values(array_unique(array_merge($business_tokens, $hint_tokens))),
            'hint_tokens' => array_values(array_unique($hint_tokens)),
            'own_brand_tokens' => $own_brand_tokens,
            'competitor_brand_tokens' => $competitor_brand_tokens,
            'catalog_items' => $catalog_items,
            'industry_hints' => $industry_hints,
            'industry_exclusions' => $this->aysa_adjustments_get_industry_exclusion_tokens((string) ($context['industry'] ?? ''), (string) ($context['website_type'] ?? '')),
        );
    }

    private function aysa_adjustments_get_industry_keyword_hints($industry, $website_type = '') {
        $industry = strtolower(trim(remove_accents((string) $industry)));
        $website_type = strtolower(trim(remove_accents((string) $website_type)));

        if (str_contains($industry, 'flor') || str_contains($website_type, 'commerce') || str_contains($website_type, 'ecommerce')) {
            return array(
                'flori', 'florarie', 'florarie online', 'buchet', 'buchete', 'aranjamente florale',
                'trandafiri', 'bujori', 'lalele', 'cadouri', 'livrare flori', 'flori cadou',
                'flori pentru evenimente', 'flori pentru aniversare', 'flori pentru nunta'
            );
        }

        return array((string) $industry, (string) $website_type);
    }

    private function aysa_adjustments_get_industry_exclusion_tokens($industry, $website_type = '') {
        $industry = strtolower(trim(remove_accents((string) $industry)));
        $website_type = strtolower(trim(remove_accents((string) $website_type)));

        if (str_contains($industry, 'flor') || str_contains($website_type, 'commerce') || str_contains($website_type, 'ecommerce')) {
            return array(
                'wifi', 'camera', 'yala', 'lac', 'unghii', 'pisici',
                'prostata', 'sinuzita', 'menopauza', 'yamato', 'btu',
                'inverter', 'aer conditionat', 'boneta', 'satin'
            );
        }

        if (str_contains($industry, 'contab') || str_contains($industry, 'factur')) {
            return array(
                'flori', 'buchet', 'trandafiri', 'bujori', 'lalele', 'aranjamente'
            );
        }

        return array();
    }

    private function aysa_adjustments_extract_brand_tokens($values) {
        $values = is_array($values) ? $values : array();
        $tokens = array();
        $skip = array('www', 'http', 'https', 'com', 'ro', 'net', 'org', 'shop', 'store', 'online');

        foreach ($values as $value) {
            $value = trim((string) $value);
            if ($value === '') {
                continue;
            }
            $parsed_host = wp_parse_url($value, PHP_URL_HOST);
            if (is_string($parsed_host) && $parsed_host !== '') {
                $host_parts = preg_split('/[.\-]+/', strtolower(trim(remove_accents($parsed_host)))) ?: array();
                foreach ($host_parts as $part) {
                    $part = trim((string) $part);
                    if ($part === '' || strlen($part) < 3 || in_array($part, $skip, true)) {
                        continue;
                    }
                    $tokens[] = $part;
                }
            }

            foreach ($this->aysa_adjustments_tokenize_keyword_text($value) as $token) {
                if (strlen($token) < 3 || in_array($token, $skip, true)) {
                    continue;
                }
                $tokens[] = $token;
            }
        }

        return array_values(array_unique($tokens));
    }

    private function aysa_adjustments_get_competitor_brand_tokens($project_id, $protected_tokens = array()) {
        $snapshot = $this->aysa_adjustments_get_competitor_identification_snapshot($project_id);
        $rows = isset($snapshot['rows']) && is_array($snapshot['rows']) ? $snapshot['rows'] : array();
        $protected_tokens = array_values(array_unique(array_map(function($token) {
            return strtolower(trim(remove_accents((string) $token)));
        }, is_array($protected_tokens) ? $protected_tokens : array())));
        $skip = array('www', 'http', 'https', 'com', 'ro', 'net', 'org', 'shop', 'store', 'online', 'facebook', 'instagram');

        $tokens = array();
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $domain = trim((string) ($row['domain'] ?? ''));
            if ($domain === '') {
                continue;
            }
            foreach ($this->aysa_adjustments_extract_brand_tokens(array($domain)) as $token) {
                if (strlen($token) < 4 || in_array($token, $skip, true) || in_array($token, $protected_tokens, true)) {
                    continue;
                }
                $tokens[] = $token;
            }
        }

        return array_values(array_unique($tokens));
    }

    private function aysa_adjustments_get_wp_catalog_items() {
        $items = array();

        $terms = get_terms(array(
            'taxonomy' => 'product_cat',
            'hide_empty' => false,
        ));
        if (is_array($terms)) {
            foreach ($terms as $term) {
                if (!is_object($term)) {
                    continue;
                }
                $text = trim($term->name . ' ' . $term->slug . ' ' . wp_strip_all_tags((string) $term->description));
                $items[] = array(
                    'type' => 'Category',
                    'id' => intval($term->term_id),
                    'name' => (string) $term->name,
                    'url' => !is_wp_error(get_term_link($term)) ? (string) get_term_link($term) : '',
                    'text' => $text,
                    'tokens' => $this->aysa_adjustments_tokenize_keyword_text($text),
                );
            }
        }

        foreach (get_posts(array('post_type' => 'product', 'posts_per_page' => -1, 'post_status' => 'publish')) as $post) {
            if (!is_object($post)) {
                continue;
            }
            $text = trim($post->post_title . ' ' . $post->post_name . ' ' . wp_strip_all_tags(wp_trim_words((string) $post->post_content, 40, '')));
            $items[] = array(
                'type' => 'Product',
                'id' => intval($post->ID),
                'name' => (string) $post->post_title,
                'url' => (string) get_permalink($post->ID),
                'text' => $text,
                'tokens' => $this->aysa_adjustments_tokenize_keyword_text($text),
            );
        }

        foreach (get_posts(array('post_type' => 'page', 'posts_per_page' => -1, 'post_status' => 'publish')) as $post) {
            if (!is_object($post)) {
                continue;
            }
            $text = trim($post->post_title . ' ' . $post->post_name . ' ' . wp_strip_all_tags(wp_trim_words((string) $post->post_content, 40, '')));
            $items[] = array(
                'type' => 'Page',
                'id' => intval($post->ID),
                'name' => (string) $post->post_title,
                'url' => (string) get_permalink($post->ID),
                'text' => $text,
                'tokens' => $this->aysa_adjustments_tokenize_keyword_text($text),
            );
        }

        return $items;
    }

    private function aysa_adjustments_filter_catalog_items_for_business($catalog_items, $focus_tokens) {
        $catalog_items = is_array($catalog_items) ? $catalog_items : array();
        $focus_tokens = array_values(array_filter(array_unique(array_map(function($token) {
            return trim((string) $token);
        }, is_array($focus_tokens) ? $focus_tokens : array()))));
        if (empty($focus_tokens)) {
            return $catalog_items;
        }

        $keep = array();
        foreach ($catalog_items as $item) {
            if (!is_array($item)) {
                continue;
            }
            $item_tokens = isset($item['tokens']) && is_array($item['tokens']) ? $item['tokens'] : array();
            $item_text = strtolower(trim(remove_accents((string) ($item['text'] ?? ''))));
            $item_name = strtolower(trim(remove_accents((string) ($item['name'] ?? ''))));
            $overlap = count(array_intersect($item_tokens, $focus_tokens));
            $keep_item = ($overlap >= 1);

            if (!$keep_item) {
                foreach ($focus_tokens as $focus_token) {
                    if ($focus_token !== '' && (str_contains($item_text, $focus_token) || str_contains($item_name, $focus_token))) {
                        $keep_item = true;
                        break;
                    }
                }
            }

            if ($keep_item) {
                $keep[] = $item;
            }
        }

        return !empty($keep) ? $keep : $catalog_items;
    }

    private function aysa_adjustments_tokenize_keyword_text($text) {
        $text = strtolower(trim(remove_accents((string) $text)));
        if ($text === '') {
            return array();
        }
        $text = preg_replace('/[^a-z0-9 ]+/u', ' ', $text);
        $parts = preg_split('/\s+/', (string) $text) ?: array();
        $stopwords = array(
            'si', 'sau', 'de', 'din', 'la', 'in', 'pe', 'cu', 'fara', 'pentru', 'prin', 'un', 'o',
            'the', 'and', 'for', 'to', 'of', 'a', 'al', 'ale', 'ai', 'catre', 'despre', 'este', 'sunt',
            'fi', 'ca', 'ce', 'mai', 'sunt', 'sau', 'your', 'site'
        );
        $tokens = array();
        foreach ($parts as $token) {
            $token = trim((string) $token);
            if ($token === '' || strlen($token) < 2 || in_array($token, $stopwords, true)) {
                continue;
            }
            $tokens[] = $token;
        }
        return array_values(array_unique($tokens));
    }

    private function aysa_adjustments_get_weak_context_tokens() {
        return array(
            'bucuresti', 'bragadiru', 'romania', 'sector', 'online', 'local', 'oras', 'city'
        );
    }

    private function aysa_adjustments_match_keyword_to_wp_item($keyword, $catalog_items) {
        $keyword_tokens = $this->aysa_adjustments_tokenize_keyword_text($keyword);
        if (empty($keyword_tokens) || !is_array($catalog_items)) {
            return null;
        }

        $keyword_norm = implode(' ', $keyword_tokens);
        $best = null;
        foreach ($catalog_items as $item) {
            if (!is_array($item) || empty($item['tokens'])) {
                continue;
            }
            $item_tokens = is_array($item['tokens']) ? $item['tokens'] : array();
            $overlap = count(array_intersect($keyword_tokens, $item_tokens));
            if ($overlap <= 0) {
                continue;
            }

            $score = ($overlap / max(count($keyword_tokens), 1)) * 0.72
                + ($overlap / max(count($item_tokens), 1)) * 0.28;
            $item_text = strtolower(trim(remove_accents((string) ($item['text'] ?? ''))));
            if ($item_text !== '' && (str_contains($item_text, $keyword_norm) || str_contains($keyword_norm, strtolower(trim(remove_accents((string) ($item['name'] ?? ''))))))) {
                $score += 0.18;
            }

            if ($best === null || $score > floatval($best['score'] ?? 0)) {
                $best = array(
                    'type' => (string) ($item['type'] ?? ''),
                    'id' => intval($item['id'] ?? 0),
                    'name' => (string) ($item['name'] ?? ''),
                    'url' => (string) ($item['url'] ?? ''),
                    'score' => min(1, $score),
                );
            }
        }

        if ($best === null || floatval($best['score'] ?? 0) < 0.34) {
            return null;
        }

        return $best;
    }

    private function aysa_adjustments_score_keyword_relevance($keyword, $row, $context, $candidate = null) {
        $keyword_tokens = $this->aysa_adjustments_tokenize_keyword_text($keyword);
        if (empty($keyword_tokens)) {
            return 0;
        }

        $score = 0.0;
        $business_tokens = isset($context['business_tokens']) && is_array($context['business_tokens']) ? $context['business_tokens'] : array();
        $weak_context_tokens = $this->aysa_adjustments_get_weak_context_tokens();
        $strong_business_tokens = array_values(array_diff($business_tokens, $weak_context_tokens));
        $strong_overlap = count(array_intersect($keyword_tokens, $strong_business_tokens));
        $weak_overlap = count(array_intersect($keyword_tokens, $weak_context_tokens));
        $overlap = $strong_overlap + $weak_overlap;
        if ($strong_overlap > 0) {
            $score += min(0.58, $strong_overlap * 0.2);
        }
        if ($weak_overlap > 0) {
            $score += min(0.08, $weak_overlap * 0.03);
        }

        $keyword_norm = strtolower(trim(remove_accents((string) $keyword)));
        $seed_keywords = isset($context['seed_keywords']) && is_array($context['seed_keywords']) ? $context['seed_keywords'] : array();
        foreach ($seed_keywords as $seed_keyword) {
            $seed_norm = strtolower(trim(remove_accents((string) $seed_keyword)));
            if ($seed_norm === '') {
                continue;
            }
            if (str_contains($keyword_norm, $seed_norm) || str_contains($seed_norm, $keyword_norm)) {
                $score = max($score, 0.78);
                break;
            }

            $seed_tokens = $this->aysa_adjustments_tokenize_keyword_text($seed_keyword);
            $seed_strong_tokens = array_values(array_diff($seed_tokens, $weak_context_tokens));
            $seed_overlap = count(array_intersect($keyword_tokens, $seed_strong_tokens));
            if ($seed_overlap > 0) {
                $score = max($score, min(0.76, ($seed_overlap / max(count($seed_strong_tokens), 1)) * 0.76));
            }
        }

        if (is_array($candidate) && !empty($candidate['id'])) {
            $score = max($score, min(0.92, 0.42 + (floatval($candidate['score'] ?? 0) * 0.5)));
        }

        $intent = strtolower(trim((string) ($row['intent'] ?? '')));
        if (in_array($intent, array('transactional', 'commercial'), true)) {
            $score += 0.05;
        }

        if (intval($row['search_volume'] ?? 0) > 0) {
            $score += 0.02;
        }

        if ($overlap <= 0 && !is_array($candidate)) {
            $score -= 0.2;
        }

        $source_keys = isset($row['source_keys']) && is_array($row['source_keys']) ? $row['source_keys'] : array();
        if (!empty($source_keys['competitor_gap']) && empty($source_keys['gsc']) && empty($source_keys['suggestions']) && !is_array($candidate) && $strong_overlap <= 1) {
            $score -= 0.24;
        }

        $disallowed_tokens = isset($context['industry_exclusions']) && is_array($context['industry_exclusions'])
            ? $context['industry_exclusions']
            : array();
        $disallowed_overlap = count(array_intersect($keyword_tokens, $disallowed_tokens));
        if ($disallowed_overlap > 0 && $strong_overlap <= 0) {
            $score -= min(0.45, 0.16 * $disallowed_overlap);
        }

        $competitor_brand_tokens = isset($context['competitor_brand_tokens']) && is_array($context['competitor_brand_tokens'])
            ? $context['competitor_brand_tokens']
            : array();
        $own_brand_tokens = isset($context['own_brand_tokens']) && is_array($context['own_brand_tokens'])
            ? $context['own_brand_tokens']
            : array();
        $competitor_overlap = count(array_intersect($keyword_tokens, $competitor_brand_tokens));
        $own_brand_overlap = count(array_intersect($keyword_tokens, $own_brand_tokens));
        if ($competitor_overlap > 0 && $own_brand_overlap <= 0) {
            $score -= min(0.55, 0.22 * $competitor_overlap);
        }

        return max(0, min(1, $score));
    }

    private function aysa_adjustments_keyword_passes_business_gate($keyword, $row, $context, $candidate = null) {
        $keyword_tokens = $this->aysa_adjustments_tokenize_keyword_text($keyword);
        if (empty($keyword_tokens)) {
            return false;
        }

        $weak_context_tokens = $this->aysa_adjustments_get_weak_context_tokens();
        $hint_tokens = isset($context['hint_tokens']) && is_array($context['hint_tokens']) ? $context['hint_tokens'] : array();
        $core_tokens = array_values(array_diff($hint_tokens, $weak_context_tokens, array('flori', 'florarie')));
        $core_overlap = count(array_intersect($keyword_tokens, $core_tokens));
        $generic_overlap = count(array_intersect($keyword_tokens, array('flori', 'florarie', 'plante')));

        $seed_keywords = isset($context['seed_keywords']) && is_array($context['seed_keywords']) ? $context['seed_keywords'] : array();
        $seed_overlap = 0;
        foreach ($seed_keywords as $seed_keyword) {
            $seed_tokens = array_values(array_diff($this->aysa_adjustments_tokenize_keyword_text($seed_keyword), $weak_context_tokens));
            $seed_overlap = max($seed_overlap, count(array_intersect($keyword_tokens, $seed_tokens)));
        }

        $source_keys = isset($row['source_keys']) && is_array($row['source_keys']) ? $row['source_keys'] : array();
        $candidate_score = is_array($candidate) ? floatval($candidate['score'] ?? 0) : 0.0;
        $competitor_brand_tokens = isset($context['competitor_brand_tokens']) && is_array($context['competitor_brand_tokens'])
            ? $context['competitor_brand_tokens']
            : array();
        $own_brand_tokens = isset($context['own_brand_tokens']) && is_array($context['own_brand_tokens'])
            ? $context['own_brand_tokens']
            : array();
        $competitor_overlap = count(array_intersect($keyword_tokens, $competitor_brand_tokens));
        $own_brand_overlap = count(array_intersect($keyword_tokens, $own_brand_tokens));
        $brand_intent_tokens = array('florarie', 'flori', 'online', 'shop', 'store', 'bucuresti', 'bragadiru', 'romania');

        if ($competitor_overlap > 0 && $own_brand_overlap <= 0) {
            $brand_intent_overlap = count(array_intersect($keyword_tokens, $brand_intent_tokens));
            if ($brand_intent_overlap > 0 || count($keyword_tokens) <= 3 || $candidate_score < 0.78) {
                return false;
            }
        }

        if ($core_overlap >= 1) {
            return true;
        }

        if ($seed_overlap >= 2) {
            return true;
        }

        if (!empty($source_keys['gsc']) && ($generic_overlap >= 1 || $candidate_score >= 0.45)) {
            return true;
        }

        if (is_array($candidate) && $candidate_score >= 0.62 && $generic_overlap >= 1) {
            return true;
        }

        return false;
    }

    private function aysa_adjustments_auto_associate_selected_keywords($project_id, $matched_items) {
        $existing_rows = $this->get_keyword_discovery_local_associations($project_id);
        $existing_keywords = array();
        if (is_array($existing_rows)) {
            foreach ($existing_rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $existing_keywords[strtolower(trim((string) ($row['keyword'] ?? '')))] = true;
            }
        }

        $associated = 0;
        $matched_candidates = 0;
        foreach ($matched_items as $keyword_norm => $candidate) {
            if (!is_array($candidate) || empty($candidate['id']) || empty($candidate['type'])) {
                continue;
            }
            $matched_candidates++;
            if (!empty($existing_keywords[$keyword_norm])) {
                continue;
            }
            $this->upsert_keyword_discovery_local_association(
                $project_id,
                (string) ($candidate['keyword'] ?? $keyword_norm),
                (string) $candidate['type'],
                intval($candidate['id']),
                (string) ($candidate['name'] ?? '')
            );
            $associated++;
            $existing_keywords[$keyword_norm] = true;
        }

        return array(
            'associated' => $associated,
            'matched_candidates' => $matched_candidates,
        );
    }

    private function aysa_adjustments_run_competitor_identification_sync($project_id, $setup_user_id = 0) {
        $setup_user_id = intval($setup_user_id);
        if ($setup_user_id <= 0) {
            $setup_user_id = intval($this->aysa_get_authenticated_external_user_id());
            if ($setup_user_id <= 0) {
                $setup_user_id = intval(get_option('aysa_user_id', 0));
            }
            if ($setup_user_id <= 0) {
                $setup_user_id = get_current_user_id();
            }
        }

        $seed_data = $this->aysa_keyword_strategy_extract_seed_data($setup_user_id);
        $seed_keywords = isset($seed_data['seed_keywords']) && is_array($seed_data['seed_keywords']) ? array_values($seed_data['seed_keywords']) : array();
        $configured_competitors = isset($seed_data['competitors']) && is_array($seed_data['competitors']) ? array_values($seed_data['competitors']) : array();
        $configured_competitors = array_values(array_unique(array_filter(array_map(function($domain) {
            return $this->aysa_monitoring_extract_host((string) $domain);
        }, $configured_competitors), function($domain) {
            return $domain !== '';
        })));
        if (empty($seed_keywords)) {
            return $this->aysa_adjustments_get_competitor_identification_snapshot($project_id);
        }
        $home_host = $this->aysa_monitoring_extract_host((string) home_url('/'));
        $resolved_context = $this->aysa_monitoring_resolve_context($home_host);
        $payload = array(
            'project_id' => intval($project_id),
            'seed_keywords' => array_values($seed_keywords),
            'active_competitors' => array_values($configured_competitors),
            'location_code' => intval($resolved_context['location_code'] ?? 2840),
            'language_code' => strtolower(trim((string) ($resolved_context['language_code'] ?? 'ro'))),
            'top_n' => 10,
            'own_domains' => array_values(array_filter(array_unique(array($home_host, trim((string) ($resolved_context['target'] ?? '')))))),
        );
        $result = $this->aysa_broker_request('POST', '/v1/competitors/identify/from-seeds', $payload, 180);
        if (!is_wp_error($result)) {
            $data = isset($result['data']) && is_array($result['data']) ? $result['data'] : $result;
            if (is_array($data) && !empty($data['ok']) && isset($data['unique_competitors'])) {
                $data = $this->aysa_keyword_v2_finalize_snapshot_contract($project_id, 'competitor_identification_v2', $data, 'unique_competitors');
                $this->aysa_competitor_identification_save_cached_payload($project_id, $payload, $data);
            }
        }
        return $this->aysa_adjustments_get_competitor_identification_snapshot($project_id);
    }

    private function aysa_adjustments_run_competitor_backlinks_sync($project_id, $setup_user_id = 0) {
        $this->handle_competitor_backlinks_gap_insights_for_workspace($project_id, $setup_user_id);
        return $this->aysa_adjustments_get_competitor_backlinks_snapshot($project_id);
    }

    private function handle_competitor_backlinks_gap_insights_for_workspace($project_id, $setup_user_id = 0) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return;
        }
        $home_host = $this->aysa_monitoring_extract_host((string) home_url('/'));
        $resolved_context = $this->aysa_monitoring_resolve_context($home_host);
        $target = strtolower(trim((string) ($resolved_context['target'] ?? '')));
        if ($target !== '') {
            $target = $this->aysa_monitoring_extract_host($target);
        }
        if ($target === '') {
            $target = $home_host;
        }
        $seed_data = $this->aysa_keyword_strategy_extract_seed_data($setup_user_id > 0 ? $setup_user_id : get_current_user_id());
        $competitors = isset($seed_data['competitors']) && is_array($seed_data['competitors']) ? $seed_data['competitors'] : array();
        $normalized_competitors = array();
        foreach ($competitors as $raw) {
            $host = $this->aysa_monitoring_extract_host((string) $raw);
            if ($host === '') {
                $host = strtolower(trim((string) $raw));
            }
            $host = preg_replace('/^www\./', '', $host);
            if ($host === '' || $host === $target || !preg_match('/^[a-z0-9.-]+\.[a-z]{2,}$/', $host)) {
                continue;
            }
            $normalized_competitors[$host] = true;
        }

        if (empty($normalized_competitors)) {
            $identified_snapshot = $this->aysa_adjustments_get_competitor_identification_snapshot($project_id);
            $identified_rows = isset($identified_snapshot['rows']) && is_array($identified_snapshot['rows']) ? $identified_snapshot['rows'] : array();
            foreach ($identified_rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $host = strtolower(trim((string) ($row['domain'] ?? '')));
                $host = preg_replace('/^www\./', '', $host);
                if ($host === '' || $host === $target || !preg_match('/^[a-z0-9.-]+\.[a-z]{2,}$/', $host)) {
                    continue;
                }
                $normalized_competitors[$host] = true;
            }
        }

        $domains = array_merge(array($target), array_keys($normalized_competitors));
        $profiles = array();
        foreach ($domains as $domain) {
            $broker_result = $this->aysa_broker_request('POST', '/v1/dataforseo/backlink-audit', array(
                'target' => $domain,
                'domain' => $domain,
                'limit' => 150,
                'project_id' => $project_id,
            ), 180);
            if (is_wp_error($broker_result)) {
                continue;
            }
            $items = (isset($broker_result['items']) && is_array($broker_result['items'])) ? $broker_result['items'] : array();
            $snapshot = array(
                'domain' => $domain,
                'provider' => (string) ($broker_result['provider'] ?? 'dataforseo'),
                'generated_at' => current_time('mysql'),
                'count' => count($items),
                'items' => $items,
            );
            update_option($this->aysa_get_backlink_audit_domain_key($project_id, $domain), $snapshot, false);
            $profiles[$domain] = $this->aysa_backlinks_build_profile($domain, $items);
        }
        if (!empty($profiles)) {
            $target_profile = isset($profiles[$target]) ? $profiles[$target] : $this->aysa_backlinks_empty_profile($target);
            $competitor_profiles = array();
            foreach (array_keys($normalized_competitors) as $domain) {
                $competitor_profiles[] = isset($profiles[$domain]) ? $profiles[$domain] : $this->aysa_backlinks_empty_profile($domain);
            }
            $brand_terms = $this->aysa_backlinks_extract_brand_terms($target, $setup_user_id > 0 ? $setup_user_id : get_current_user_id());
            $benchmark = $this->aysa_backlinks_build_benchmark($competitor_profiles, $brand_terms);
            $gap = $this->aysa_backlinks_build_gap($target_profile, $benchmark, $brand_terms);
            update_option('aysa_backlink_summary_' . intval($project_id), array(
                'generated_at' => current_time('mysql'),
                'items' => array_values($profiles),
                'target_profile' => $target_profile,
                'competitor_profiles' => $competitor_profiles,
                'benchmark' => $benchmark,
                'gap' => $gap,
                'brand_terms' => $brand_terms,
            ), false);
        }
    }

    private function aysa_adjustments_execute_research_action($action, $project_id, $setup_user_id, $language) {
        $action = (string) $action;
        if (strpos($action, 'wallet_topup_buy:') === 0) {
            $credits_cents = intval(substr($action, strlen('wallet_topup_buy:')));
            return $this->aysa_adjustments_create_wallet_topup_order($project_id, $credits_cents, $language, array(
                'module' => 'billing_wallet',
                'action_key' => 'wallet_topup_buy',
                'stage_key' => 'research_adjustments',
                'paused_action_ref' => 'wallet_topup_buy:' . $credits_cents,
                'requested_credits' => $credits_cents,
            ));
        }
        if (strpos($action, 'apply_preview_changes:') === 0) {
            $encoded = substr($action, strlen('apply_preview_changes:'));
            $decoded = base64_decode(strtr($encoded, '-_', '+/'), true);
            $payload = json_decode((string) $decoded, true);
            if (!is_array($payload)) {
                return array(
                    'module' => 'onpage_optimization',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I could not read the approved preview payload, so I stopped before queuing any changes.',
                        'Nu am putut citi payload-ul preview aprobat, așa că m-am oprit înainte să trimit modificările în review.',
                        'Не успях да прочета одобрения preview payload, затова спрях преди да изпратя промените за review.'
                    ),
                );
            }

            return $this->aysa_adjustments_apply_preview_changes_to_pending_reviews($project_id, $payload, $language);
        }

        if (strpos($action, 'technical_redirects_apply:') === 0) {
            $encoded = substr($action, strlen('technical_redirects_apply:'));
            $decoded = base64_decode(strtr($encoded, '-_', '+/'), true);
            $payload = json_decode((string) $decoded, true);
            if (!is_array($payload)) {
                return array(
                    'module' => 'technical_404_errors',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I could not read the redirect preview payload, so I stopped before changing any redirect rules.',
                        'Nu am putut citi payload-ul preview pentru redirecturi, așa că m-am oprit înainte să schimb vreo regulă.',
                        'Не успях да прочета preview payload-а за redirect-ите, затова спрях преди да променя каквито и да било правила.'
                    ),
                );
            }

            return $this->aysa_adjustments_apply_redirect_preview($payload, $language);
        }

        if ($action === 'monitoring_refresh_start') {
            $snapshot = $this->aysa_adjustments_get_monitoring_ranking_snapshot($project_id);
            $tracked = intval($snapshot['tracked_count'] ?? 0);
            $message = match ($language) {
                'ro' => 'Nu pornesc refresh-ul direct. Pregătesc mai întâi aprobarea: targeturi incluse/excluse, estimare de cost, limită maximă și snapshotul care va fi salvat. Keyworduri urmărite: ' . $tracked . '.',
                'bg' => 'Не стартирам refresh директно. Първо подготвям одобрението: включени/изключени target-и, оценка на разхода, максимален лимит и snapshot-а, който ще бъде записан. Следени keyword-и: ' . $tracked . '.',
                default => 'I do not start refresh directly. I prepare approval first: included/excluded targets, cost estimate, maximum cap, and the snapshot that will be saved. Tracked keywords: ' . $tracked . '.',
            };

            return array(
                'module' => 'monitoring_ranking',
                'assistant_message' => $message,
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'Prepare refresh approval', 'Pregătește aprobarea refresh-ului', 'Подготви одобрение за refresh'),
                        'message' => '',
                        'ui_action' => 'refresh_requires_approval',
                    ),
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See ranking rows', 'Vezi rândurile de ranking', 'Виж ranking редовете'),
                        'message' => '',
                        'ui_action' => 'open_artifact_monitoring_ranking',
                    ),
                ),
                'ui_action' => 'refresh_requires_approval',
                'execution_action' => 'monitoring_refresh_preflight',
            );
        }
        if ($action === 'monitoring_refresh_status') {
            $snapshot = $this->aysa_adjustments_get_monitoring_ranking_snapshot($project_id);
            $status = (string) ($snapshot['job_status'] ?? 'idle');
            $processed = intval($snapshot['job_processed'] ?? 0);
            $total = intval($snapshot['job_total'] ?? 0);
            $failed = intval($snapshot['job_failed'] ?? 0);
            $stale = intval($snapshot['stale_count'] ?? 0);
            $message = match ($language) {
                'ro' => 'Status refresh ranking: `' . $status . '` (' . $processed . '/' . $total . '). Ai ' . $stale . ' keyworduri încă stale și ' . $failed . ' eșecuri în jobul curent.',
                'bg' => 'Статус на ranking refresh: `' . $status . '` (' . $processed . '/' . $total . '). Имаш ' . $stale . ' все още stale keyword-а и ' . $failed . ' неуспеха в текущия job.',
                default => 'Ranking refresh status: `' . $status . '` (' . $processed . '/' . $total . '). You still have ' . $stale . ' stale keywords and ' . $failed . ' failures in the current job.',
            };

            return array(
                'module' => 'monitoring_ranking',
                'assistant_message' => $message,
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See job status', 'Vezi statusul jobului', 'Виж статуса на job-а'),
                        'message' => '',
                        'ui_action' => 'open_artifact_monitoring_status',
                    ),
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See stale keywords', 'Vezi keywordurile stale', 'Виж stale keyword-ите'),
                        'message' => '',
                        'ui_action' => 'open_artifact_monitoring_stale',
                    ),
                ),
                'ui_action' => 'open_artifact_monitoring_status',
            );
        }
        if ($action === 'offpage_link_insertion_recommend') {
            return $this->aysa_adjustments_recommend_link_insertion_portals($project_id, $setup_user_id, $language);
        }
        if (strpos($action, 'offpage_link_insertion_place:') === 0) {
            $encoded = substr($action, strlen('offpage_link_insertion_place:'));
            $decoded = base64_decode(strtr($encoded, '-_', '+/'));
            $payload = json_decode((string) $decoded, true);
            if (!is_array($payload)) {
                return array(
                    'module' => 'offpage_link_building',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I could not understand the link insertion order details well enough to place the order.',
                        'Nu am reușit să înțeleg suficient de bine detaliile comenzii de link insertion ca să plasez comanda.',
                        'Не успях да разбера достатъчно добре детайлите за link insertion поръчката, за да я пусна.'
                    ),
                );
            }

            return $this->aysa_adjustments_place_link_insertion_order($project_id, $setup_user_id, $payload, $language);
        }
        if ($action === 'offpage_seo_article_recommend') {
            return $this->aysa_adjustments_recommend_article_portals($project_id, $setup_user_id, 'seo_article', $language);
        }
        if ($action === 'publisher_article_draft_open_latest') {
            $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/publisher-articles/list', array(
                'project_id' => intval($project_id),
                'status' => 'draft',
                'limit' => 1,
            ), 60);
            if (is_wp_error($broker_result)) {
                return array(
                    'module' => 'offpage_link_building',
                    'assistant_message' => $this->aysa_humanize_broker_error($broker_result, 'Could not load the latest publisher draft.'),
                );
            }
            $items = isset($broker_result['items']) && is_array($broker_result['items']) ? $broker_result['items'] : array();
            $draft = (!empty($items[0]) && is_array($items[0])) ? $items[0] : array();
            if (empty($draft)) {
                return array(
                    'module' => 'offpage_link_building',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I did not find any publisher draft yet.',
                        'Nu am găsit încă niciun draft pentru publisher.',
                        'Все още не намерих publisher чернова.'
                    ),
                    'ui_action' => 'open_artifact_offpage_publisher_drafts',
                );
            }

            return array(
                'module' => 'offpage_link_building',
                'assistant_message' => match ($language) {
                    'ro' => 'Am deschis draftul "' . sanitize_text_field((string) ($draft['title'] ?? 'Draft')) . '". Îl poți revizui și apoi aproba pentru publicare.',
                    'bg' => 'Отворих черновата "' . sanitize_text_field((string) ($draft['title'] ?? 'Draft')) . '". Можеш да я прегледаш и после да я одобриш за публикуване.',
                    default => 'I opened the draft "' . sanitize_text_field((string) ($draft['title'] ?? 'Draft')) . '". You can review it and then approve it for publishing.',
                },
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'Approve draft', 'Aprobă draftul', 'Одобри черновата'),
                        'message' => '',
                        'execution_action' => 'publisher_article_draft_approve:' . intval($draft['id'] ?? 0),
                    ),
                ),
                'ui_action' => 'open_artifact_offpage_publisher_drafts',
            );
        }
        if (strpos($action, 'publisher_article_draft_approve:') === 0) {
            $draft_id = intval(substr($action, strlen('publisher_article_draft_approve:')));
            if ($draft_id <= 0) {
                return array(
                    'module' => 'offpage_link_building',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I could not understand which draft should be approved.',
                        'Nu am reușit să înțeleg ce draft trebuie aprobat.',
                        'Не успях да разбера коя чернова трябва да бъде одобрена.'
                    ),
                );
            }
            $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/publisher-articles/approve', array(
                'draft_id' => $draft_id,
            ), 60);
            if (is_wp_error($broker_result)) {
                return array(
                    'module' => 'offpage_link_building',
                    'assistant_message' => $this->aysa_humanize_broker_error($broker_result, 'Could not approve the publisher draft.'),
                );
            }
            $draft = isset($broker_result['draft']) && is_array($broker_result['draft']) ? $broker_result['draft'] : array();
            return array(
                'module' => 'offpage_link_building',
                'assistant_message' => match ($language) {
                    'ro' => 'Am aprobat draftul pentru publicare. Îl mut automat în Published Articles după ce comanda devine live și apare URL-ul public.',
                    'bg' => 'Одобрих черновата за публикуване. Ще я преместя автоматично в Published Articles, след като поръчката стане live и се появи публичният URL.',
                    default => 'I approved the draft for publishing. I will move it automatically into Published Articles once the order goes live and a public URL appears.',
                },
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See published articles', 'Vezi articolele publicate', 'Виж публикуваните статии'),
                        'message' => '',
                        'ui_action' => 'open_artifact_offpage_publisher_published',
                    ),
                ),
                'ui_action' => 'open_artifact_offpage_publisher_published',
            );
        }
        if (strpos($action, 'offpage_seo_article_place:') === 0) {
            $encoded = substr($action, strlen('offpage_seo_article_place:'));
            $decoded = base64_decode(strtr($encoded, '-_', '+/'));
            $payload = json_decode((string) $decoded, true);
            if (!is_array($payload)) {
                return array(
                    'module' => 'offpage_link_building',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I could not understand the SEO article order details well enough to place the order.',
                        'Nu am reușit să înțeleg suficient de bine detaliile comenzii de SEO article ca să plasez comanda.',
                        'Не успях да разбера достатъчно добре детайлите за SEO article поръчката, за да я пусна.'
                    ),
                );
            }

            return $this->aysa_adjustments_place_article_order($project_id, $setup_user_id, $payload, 'seo_article', $language);
        }
        if ($action === 'offpage_professional_article_recommend') {
            return $this->aysa_adjustments_recommend_article_portals($project_id, $setup_user_id, 'professional_article', $language);
        }
        if (strpos($action, 'offpage_professional_article_place:') === 0) {
            $encoded = substr($action, strlen('offpage_professional_article_place:'));
            $decoded = base64_decode(strtr($encoded, '-_', '+/'));
            $payload = json_decode((string) $decoded, true);
            if (!is_array($payload)) {
                return array(
                    'module' => 'offpage_link_building',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I could not understand the professional article order details well enough to place the order.',
                        'Nu am reușit să înțeleg suficient de bine detaliile comenzii de professional article ca să plasez comanda.',
                        'Не успях да разбера достатъчно добре детайлите за professional article поръчката, за да я пусна.'
                    ),
                );
            }

            return $this->aysa_adjustments_place_article_order($project_id, $setup_user_id, $payload, 'professional_article', $language);
        }
        if ($action === 'offpage_seo_text_ads_recommend') {
            return $this->aysa_adjustments_recommend_seo_text_ads_portals($project_id, $setup_user_id, $language);
        }
        if (strpos($action, 'offpage_seo_text_ads_place:') === 0) {
            $encoded = substr($action, strlen('offpage_seo_text_ads_place:'));
            $decoded = base64_decode(strtr($encoded, '-_', '+/'));
            $payload = json_decode((string) $decoded, true);
            if (!is_array($payload)) {
                return array(
                    'module' => 'offpage_link_building',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I could not understand the SEO Text Ads order details well enough to place the order.',
                        'Nu am reușit să înțeleg suficient de bine detaliile comenzii SEO Text Ads ca să plasez comanda.',
                        'Не успях да разбера достатъчно добре детайлите за SEO Text Ads, за да пусна поръчката.'
                    ),
                );
            }

            return $this->aysa_adjustments_place_seo_text_ads_order($project_id, $setup_user_id, $payload, $language);
        }
        if (strpos($action, 'content_create_item:') === 0) {
            $encoded = substr($action, strlen('content_create_item:'));
            $decoded = base64_decode(strtr($encoded, '-_', '+/'));
            $payload = json_decode((string) $decoded, true);
            if (!is_array($payload)) {
                return array(
                    'module' => 'content_strategy',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I could not understand the content creation request well enough to execute it.',
                        'Nu am reușit să înțeleg suficient de bine cererea de creare conținut ca să o execut.',
                        'Не успях да разбера достатъчно добре заявката за създаване на съдържание, за да я изпълня.'
                    ),
                );
            }

            return $this->aysa_adjustments_create_content_item($project_id, $setup_user_id, $payload, $language);
        }
        if (strpos($action, 'content_optimize_item:') === 0) {
            $encoded = substr($action, strlen('content_optimize_item:'));
            $decoded = base64_decode(strtr($encoded, '-_', '+/'));
            $payload = json_decode((string) $decoded, true);
            if (!is_array($payload)) {
                return array(
                    'module' => 'onpage_optimization',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I could not understand the optimization request well enough to execute it.',
                        'Nu am reușit să înțeleg suficient de bine cererea de optimizare ca să o execut.',
                        'Не успях да разбера достатъчно добре заявката за оптимизация, за да я изпълня.'
                    ),
                );
            }

            return $this->aysa_adjustments_optimize_content_item($project_id, $payload, $language);
        }
        if (strpos($action, 'content_strategy_generate:') === 0) {
            $strategy_type = sanitize_key(substr($action, strlen('content_strategy_generate:')));
            if ($strategy_type === '') {
                $strategy_type = 'local_seo';
            }

            return $this->aysa_adjustments_run_content_strategy_generation($project_id, $setup_user_id, $strategy_type, array(), $language);
        }
        if (strpos($action, 'content_strategy_generate_custom:') === 0) {
            $encoded = substr($action, strlen('content_strategy_generate_custom:'));
            $decoded = base64_decode(strtr($encoded, '-_', '+/'));
            $payload = json_decode((string) $decoded, true);
            if (!is_array($payload)) {
                return array(
                    'module' => 'content_strategy',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I could not understand the approved content preview well enough to execute it.',
                        'Nu am reușit să înțeleg suficient de bine preview-ul aprobat de conținut ca să-l execut.',
                        'Не успях да разбера достатъчно добре одобреното content preview, за да го изпълня.'
                    ),
                );
            }

            $strategy_type = sanitize_key((string) ($payload['strategy_type'] ?? 'competitor_derived_titles'));
            $custom_payload = isset($payload['payload']) && is_array($payload['payload']) ? $payload['payload'] : array();
            if ($strategy_type === '') {
                $strategy_type = 'competitor_derived_titles';
            }

            return $this->aysa_adjustments_run_content_strategy_generation($project_id, $setup_user_id, $strategy_type, $custom_payload, $language);
        }
        if (strpos($action, 'content_strategy_delete_batch:') === 0) {
            $plan_batch = sanitize_text_field(substr($action, strlen('content_strategy_delete_batch:')));
            $result = $this->aysa_adjustments_delete_content_strategy_batch($project_id, $plan_batch);

            return array(
                'module' => 'content_strategy',
                'assistant_message' => match ($language) {
                    'ro' => 'Am șters batch-ul generat acum din planul de conținut. Am eliminat ' . intval($result['deleted'] ?? 0) . ' idei.',
                    'bg' => 'Изтрих току-що генерирания batch от content плана. Премахнах ' . intval($result['deleted'] ?? 0) . ' идеи.',
                    default => 'I removed the batch generated just now from the content plan. I deleted ' . intval($result['deleted'] ?? 0) . ' ideas.',
                },
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See content plan', 'Vezi planul de conținut', 'Виж content плана'),
                        'message' => '',
                        'ui_action' => 'open_artifact_content_strategy',
                    ),
                ),
                'state_meta' => array(
                    'last_generated_content_plan_batch' => '',
                ),
            );
        }
        if ($action === 'keyword_suggestions_refresh') {
            $start = $this->aysa_keyword_discovery_start_background_refresh($project_id, true);
            if (is_wp_error($start)) {
                return array(
                    'module' => 'keyword_research',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I could not start the keyword refresh right now.',
                        'Nu am reușit să pornesc acum refresh-ul de keyworduri.',
                        'Не успях да стартирам keyword refresh-а в момента.'
                    ) . ' ' . $start->get_error_message(),
                    'ui_action' => 'open_keyword_discovery',
                );
            }

            $status = isset($start['status']) && is_array($start['status']) ? $start['status'] : array();
            $pending_item = isset($start['pending_item']) && is_array($start['pending_item']) ? $start['pending_item'] : $this->aysa_keyword_discovery_build_pending_item($project_id);
            $already_running = !empty($start['already_running']);

            return array(
                'module' => 'keyword_research',
                'assistant_message' => $already_running ? match ($language) {
                    'ro' => 'Keyword refresh rulează deja în background. Îți arăt progresul aici și te anunț imediat când termină.',
                    'bg' => 'Keyword refresh вече върви във фонов режим. Ще ти показвам прогреса тук и ще те уведомя веднага щом приключи.',
                    default => 'The keyword refresh is already running in the background. I will show you the progress here and notify you as soon as it finishes.',
                } : match ($language) {
                    'ro' => 'Am pornit în background refresh-ul de keyworduri. Poți continua conversația, iar eu îți arăt progresul și revin imediat ce am setul nou pregătit.',
                    'bg' => 'Стартирах keyword refresh-а във фонов режим. Можеш да продължиш разговора, а аз ще ти показвам прогреса и ще се върна веднага щом новият набор е готов.',
                    default => 'I started the keyword refresh in the background. You can keep going, and I will show you the progress and come back as soon as the new set is ready.',
                },
                'suggested_actions' => array_values(array_filter(array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See refresh status', 'Vezi statusul refresh-ului', 'Виж refresh статуса'),
                        'message' => '',
                        'ui_action' => 'open_keyword_discovery',
                    ),
                    !$already_running ? array(
                        'label' => $this->aysa_adjustments_tr($language, 'Open Keyword Discovery', 'Deschide Keyword Discovery', 'Отвори Keyword Discovery'),
                        'message' => '',
                        'ui_action' => 'open_keyword_discovery',
                    ) : array(),
                ))),
                'ui_action' => 'open_keyword_discovery',
                'state_meta' => array(
                    'pending_generated_item_id' => intval($pending_item['post_id'] ?? $project_id),
                    'pending_generated_item_title' => (string) ($pending_item['title'] ?? 'Keyword refresh'),
                    'pending_generated_item_type' => (string) ($pending_item['item_type'] ?? 'keyword_refresh'),
                    'pending_generated_item_key' => (string) ($pending_item['item_key'] ?? ('keyword_refresh:' . $project_id)),
                    'keyword_refresh_status' => (string) ($status['status'] ?? 'running'),
                ),
            );
        }
        if ($action === 'keyword_refresh_status') {
            $status = $this->aysa_keyword_discovery_get_status($project_id);
            return array(
                'module' => 'keyword_research',
                'assistant_message' => match ($language) {
                    'ro' => 'Status keyword refresh: `' . (string) ($status['status'] ?? 'idle') . '` (' . intval($status['processed'] ?? 0) . '/' . intval($status['total'] ?? 0) . '). Am staged ' . intval($status['staged'] ?? 0) . ', inserate ' . intval($status['inserted'] ?? 0) . ', actualizate ' . intval($status['updated'] ?? 0) . '.' . ((string) ($status['metrics_notice'] ?? '') !== '' ? ' ' . (string) $status['metrics_notice'] : ''),
                    'bg' => 'Статус на keyword refresh: `' . (string) ($status['status'] ?? 'idle') . '` (' . intval($status['processed'] ?? 0) . '/' . intval($status['total'] ?? 0) . '). Staged ' . intval($status['staged'] ?? 0) . ', нови ' . intval($status['inserted'] ?? 0) . ', обновени ' . intval($status['updated'] ?? 0) . '.' . ((string) ($status['metrics_notice'] ?? '') !== '' ? ' ' . (string) $status['metrics_notice'] : ''),
                    default => 'Keyword refresh status: `' . (string) ($status['status'] ?? 'idle') . '` (' . intval($status['processed'] ?? 0) . '/' . intval($status['total'] ?? 0) . '). Staged ' . intval($status['staged'] ?? 0) . ', inserted ' . intval($status['inserted'] ?? 0) . ', updated ' . intval($status['updated'] ?? 0) . '.' . ((string) ($status['metrics_notice'] ?? '') !== '' ? ' ' . (string) $status['metrics_notice'] : ''),
                },
                'ui_action' => 'open_keyword_discovery',
            );
        }
        if ($action === 'keyword_apply_staged') {
            $snapshot = get_option($this->aysa_keyword_discovery_snapshot_key($project_id), array());
            $keywords = (is_array($snapshot) && isset($snapshot['keywords']) && is_array($snapshot['keywords'])) ? $snapshot['keywords'] : array();
            if (empty($keywords)) {
                return array(
                    'module' => 'keyword_research',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'There are no staged keyword suggestions to apply right now.',
                        'Nu există acum sugestii staged de keyworduri pe care să le aplic.',
                        'В момента няма staged предложения за keywords, които да приложа.'
                    ),
                    'ui_action' => 'open_keyword_discovery',
                );
            }

            $persist = $this->aysa_keyword_discovery_persist_keywords($project_id, $keywords);
            $this->aysa_adjustments_add_pending_approval(
                $project_id,
                'keyword_research_review',
                'Keyword research is ready for review',
                match ($language) {
                    'ro' => 'Keyword research este pregatit. Am actualizat datasetul local si am nevoie de acordul tau ca sa stabilim setul urmarit, targeturile si pasii urmatori.',
                    'bg' => 'Keyword research е готов. Обнових локалния dataset и ми трябва твоето одобрение, за да зададем tracked набора, target-ите и следващите стъпки.',
                    default => 'Keyword research is ready. I updated the local dataset and now I need your approval to set the tracked set, the targets, and the next steps.',
                },
                'open_keyword_discovery'
            );
            return array(
                'module' => 'keyword_research',
                'assistant_message' => match ($language) {
                    'ro' => 'Am aplicat staged keywords în datasetul local. Inserate: ' . intval($persist['inserted'] ?? 0) . ', actualizate: ' . intval($persist['updated'] ?? 0) . ', total procesate: ' . count($keywords) . '.',
                    'bg' => 'Приложих staged keywords в локалния dataset. Нови: ' . intval($persist['inserted'] ?? 0) . ', обновени: ' . intval($persist['updated'] ?? 0) . ', общо обработени: ' . count($keywords) . '.',
                    default => 'I applied the staged keywords into the local dataset. Inserted: ' . intval($persist['inserted'] ?? 0) . ', updated: ' . intval($persist['updated'] ?? 0) . ', total processed: ' . count($keywords) . '.',
                },
                'ui_action' => 'open_keyword_discovery',
            );
        }
        if (strpos($action, 'keyword_association_upsert:') === 0) {
            $encoded = substr($action, strlen('keyword_association_upsert:'));
            $decoded = base64_decode(strtr($encoded, '-_', '+/'), true);
            $payload = json_decode((string) $decoded, true);
            if (!is_array($payload)) {
                return array(
                    'module' => 'keyword_research',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I could not understand the keyword mapping payload well enough to save the association.',
                        'Nu am reusit sa inteleg suficient de bine payload-ul de mapare ca sa salvez asocierea.',
                        'Не успях да разбера достатъчно добре payload-а за mapping, за да запазя свързването.'
                    ),
                    'ui_action' => 'open_artifact_matches',
                );
            }

            return $this->aysa_adjustments_execute_keyword_association_upsert($project_id, $payload, $language);
        }
        if (strpos($action, 'keyword_target_position_set:') === 0) {
            $encoded = substr($action, strlen('keyword_target_position_set:'));
            $decoded = base64_decode(strtr($encoded, '-_', '+/'), true);
            $payload = json_decode((string) $decoded, true);
            if (!is_array($payload)) {
                return array(
                    'module' => 'keyword_research',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I could not understand the keyword target payload well enough to update the desired position.',
                        'Nu am reusit sa inteleg suficient de bine payload-ul de target ca sa actualizez pozitia dorita.',
                        'Не успях да разбера достатъчно добре payload-а за target, за да обновя desired position.'
                    ),
                    'ui_action' => 'open_artifact_matches',
                );
            }

            return $this->aysa_adjustments_execute_keyword_target_position_set($project_id, $payload, $language);
        }
        if (strpos($action, 'keyword_primary_set:') === 0) {
            $encoded = substr($action, strlen('keyword_primary_set:'));
            $decoded = base64_decode(strtr($encoded, '-_', '+/'), true);
            $payload = json_decode((string) $decoded, true);
            if (!is_array($payload)) {
                return array(
                    'module' => 'keyword_research',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I could not understand the primary keyword payload well enough to save it.',
                        'Nu am reusit sa inteleg suficient de bine payload-ul pentru keywordul principal ca sa il salvez.',
                        'Не успях да разбера достатъчно добре payload-а за primary keyword, за да го запазя.'
                    ),
                    'ui_action' => 'open_keyword_discovery',
                );
            }

            return $this->aysa_adjustments_execute_keyword_primary_set($project_id, $payload, $language);
        }
        if (strpos($action, 'keyword_supporting_add:') === 0) {
            $encoded = substr($action, strlen('keyword_supporting_add:'));
            $decoded = base64_decode(strtr($encoded, '-_', '+/'), true);
            $payload = json_decode((string) $decoded, true);
            if (!is_array($payload)) {
                return array(
                    'module' => 'keyword_research',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I could not understand the supporting keyword payload well enough to save it.',
                        'Nu am reusit sa inteleg suficient de bine payload-ul pentru keywordul de suport ca sa il salvez.',
                        'Не успях да разбера достатъчно добре payload-а за supporting keyword, за да го запазя.'
                    ),
                    'ui_action' => 'open_keyword_discovery',
                );
            }

            return $this->aysa_adjustments_execute_keyword_supporting_add($project_id, $payload, $language);
        }
        if (strpos($action, 'keyword_cluster_map_to_entity:') === 0) {
            $encoded = substr($action, strlen('keyword_cluster_map_to_entity:'));
            $decoded = base64_decode(strtr($encoded, '-_', '+/'), true);
            $payload = json_decode((string) $decoded, true);
            if (!is_array($payload)) {
                return array(
                    'module' => 'keyword_research',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I could not understand the keyword cluster payload well enough to save the cluster mapping.',
                        'Nu am reusit sa inteleg suficient de bine payload-ul clusterului ca sa salvez maparea clusterului.',
                        'Не успях да разбера достатъчно добре payload-а на cluster-а, за да запазя mapping-а му.'
                    ),
                    'ui_action' => 'open_keyword_discovery',
                );
            }

            return $this->aysa_adjustments_execute_keyword_cluster_map_to_entity($project_id, $payload, $language);
        }
        if (strpos($action, 'keyword_tracking_update:') === 0) {
            $encoded = substr($action, strlen('keyword_tracking_update:'));
            $decoded = base64_decode(strtr($encoded, '-_', '+/'), true);
            $payload = json_decode((string) $decoded, true);
            if (!is_array($payload)) {
                return array(
                    'module' => 'keyword_research',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I could not understand the tracked keywords payload well enough to save the tracking plan.',
                        'Nu am reusit sa inteleg suficient de bine payload-ul pentru keywordurile urmarite ca sa salvez planul de tracking.',
                        'Не успях да разбера достатъчно добре payload-а за tracked keywords, за да запазя tracking плана.'
                    ),
                    'ui_action' => 'open_keyword_discovery',
                );
            }

            return $this->aysa_adjustments_execute_keyword_tracking_update($project_id, $payload, $language);
        }
        if ($action === 'keyword_strategy_show') {
            $option_key = $this->aysa_keyword_strategy_option_key($project_id, $setup_user_id);
            $saved = get_option($option_key, array());
            $items = (is_array($saved) && isset($saved['items']) && is_array($saved['items'])) ? $saved['items'] : array();
            $summary = (is_array($saved) && isset($saved['summary']) && is_array($saved['summary'])) ? $saved['summary'] : array();
            if (empty($items)) {
                return array(
                    'module' => 'keyword_research',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I do not have a saved keyword strategy yet. I can generate one from the current research if you want.',
                        'Nu am încă o strategie salvată de keyworduri. Pot să generez una din research-ul curent dacă vrei.',
                        'Все още нямам записана стратегия за keywords. Мога да генерирам такава от текущия research, ако искаш.'
                    ),
                    'suggested_actions' => array(
                        array(
                            'label' => $this->aysa_adjustments_tr($language, 'Generate strategy', 'Generează strategia', 'Генерирай стратегия'),
                            'message' => '',
                            'execution_action' => 'keyword_strategy_generate',
                        ),
                    ),
                    'ui_action' => 'open_keyword_discovery',
                );
            }

            return array(
                'module' => 'keyword_research',
                'assistant_message' => match ($language) {
                    'ro' => 'Strategia de keyworduri este pregătită. Am ' . count($items) . ' keyworduri în plan, cu ' . intval($summary['associate_existing'] ?? 0) . ' de asociat pe pagini existente, ' . intval($summary['enhance_existing'] ?? 0) . ' de consolidat și ' . intval($summary['create_new_page'] ?? 0) . ' care sugerează pagini noi.',
                    'bg' => 'Keyword стратегията е готова. Имам ' . count($items) . ' keywords в плана, с ' . intval($summary['associate_existing'] ?? 0) . ' за свързване към съществуващи страници, ' . intval($summary['enhance_existing'] ?? 0) . ' за подсилване и ' . intval($summary['create_new_page'] ?? 0) . ' които подсказват нови страници.',
                    default => 'The keyword strategy is ready. I have ' . count($items) . ' keywords in the plan, with ' . intval($summary['associate_existing'] ?? 0) . ' to map to existing pages, ' . intval($summary['enhance_existing'] ?? 0) . ' to strengthen, and ' . intval($summary['create_new_page'] ?? 0) . ' suggesting new pages.',
                },
                'ui_action' => 'open_keyword_discovery',
            );
        }
        if ($action === 'keyword_strategy_generate') {
            $seed_data = $this->aysa_keyword_strategy_extract_seed_data($setup_user_id);
            $seed_keywords = is_array($seed_data['seed_keywords'] ?? null) ? $seed_data['seed_keywords'] : array();
            $competitors = is_array($seed_data['competitors'] ?? null) ? $seed_data['competitors'] : array();
            if (empty($seed_keywords)) {
                return array(
                    'module' => 'keyword_research',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I could not generate the keyword strategy because I do not have seed keywords yet.',
                        'Nu am putut genera strategia de keyworduri pentru că nu am încă seed keywords.',
                        'Не успях да генерирам keyword стратегията, защото все още нямам seed keywords.'
                    ),
                    'ui_action' => 'open_keyword_discovery',
                );
            }

            $context = $this->aysa_keyword_strategy_get_context($setup_user_id);
            $strategy = array();
            $used_remote = false;
            $broker_payload = array(
                'project_id' => $project_id,
                'seed_keywords' => $seed_keywords,
                'competitors' => $competitors,
                'targeted_country' => (string) ($context['targeted_country'] ?? ''),
                'targeted_region' => (string) ($context['targeted_region'] ?? ''),
                'targeted_city' => (string) ($context['targeted_city'] ?? ''),
                'primary_language' => (string) ($context['primary_language'] ?? ''),
                'website_type' => (string) ($context['website_type'] ?? ''),
                'industry' => (string) ($context['industry'] ?? ''),
                'max_keywords' => 250,
            );

            $remote_result = $this->aysa_broker_request('POST', '/v1/keywords/strategy/from-seeds', $broker_payload, 120);
            if (!is_wp_error($remote_result) && is_array($remote_result)) {
                $remote_data = isset($remote_result['data']) && is_array($remote_result['data']) ? $remote_result['data'] : $remote_result;
                $remote_items = isset($remote_data['items']) && is_array($remote_data['items']) ? $remote_data['items'] : array();
                if (!empty($remote_items)) {
                    $strategy = array(
                        'project_id' => intval($project_id),
                        'user_id' => intval($setup_user_id),
                        'generated_at' => current_time('mysql'),
                        'source' => 'broker',
                        'seed_keywords' => array_values($seed_keywords),
                        'competitors' => array_values($competitors),
                        'context' => $context,
                        'summary' => isset($remote_data['summary']) && is_array($remote_data['summary']) ? $remote_data['summary'] : array(),
                        'items' => array_values($remote_items),
                    );
                    $used_remote = true;
                }
            }

            if (empty($strategy)) {
                $strategy = $this->aysa_keyword_strategy_build_local_plan($project_id, $setup_user_id, $seed_keywords, $competitors, $context);
            }

            update_option($this->aysa_keyword_strategy_option_key($project_id, $setup_user_id), $strategy, false);

            return array(
                'module' => 'keyword_research',
                'assistant_message' => match ($language) {
                    'ro' => 'Am generat strategia de keyworduri cu ' . count((array) ($strategy['items'] ?? array())) . ' keyworduri prioritizate. Sursa: ' . ($used_remote ? 'broker' : 'local fallback') . '.',
                    'bg' => 'Генерирах keyword стратегията с ' . count((array) ($strategy['items'] ?? array())) . ' приоритизирани keywords. Източник: ' . ($used_remote ? 'broker' : 'local fallback') . '.',
                    default => 'I generated the keyword strategy with ' . count((array) ($strategy['items'] ?? array())) . ' prioritized keywords. Source: ' . ($used_remote ? 'broker' : 'local fallback') . '.',
                },
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'Open Keyword Discovery', 'Deschide Keyword Discovery', 'Отвори Keyword Discovery'),
                        'message' => '',
                        'ui_action' => 'open_keyword_discovery',
                    ),
                ),
                'ui_action' => 'open_keyword_discovery',
            );
        }
        return match ($action) {
            'keyword_research_sync' => array(
                'module' => 'keyword_research',
                'assistant_message' => $this->aysa_adjustments_build_keyword_research_completion_message(
                    $this->aysa_adjustments_run_keyword_research_sync($project_id, $setup_user_id),
                    $language
                ),
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'Continue', 'Continuă', 'Продължи'),
                        'message' => $this->aysa_adjustments_tr($language, 'Continue to the next step.', 'Continuă la pasul următor.', 'Продължи към следващата стъпка.'),
                    ),
                ),
            ),
            'competitor_gap_sync' => array(
                'module' => 'competitor_gap',
                'assistant_message' => (function() use ($project_id, $setup_user_id, $language) {
                    $snapshot = $this->aysa_adjustments_run_competitor_gap_sync($project_id, $setup_user_id);
                    return $this->aysa_adjustments_build_competitor_gap_completion_message($snapshot, $language);
                })(),
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See competitor gap', 'Vezi competitor gap', 'Виж competitor gap'),
                        'message' => '',
                        'ui_action' => 'open_artifact_competitor_gap',
                    ),
                ),
                'ui_action' => 'open_artifact_competitor_gap',
            ),
            'relevant_keywords_sync' => $this->aysa_adjustments_run_relevant_keywords_sync($project_id, $setup_user_id, $language),
            'competitor_identification_sync' => array(
                'module' => 'competitor_identification',
                'assistant_message' => (function() use ($project_id, $setup_user_id, $language) {
                    $snapshot = $this->aysa_adjustments_run_competitor_identification_sync($project_id, $setup_user_id);
                    return $this->aysa_adjustments_build_competitor_identification_completion_message($snapshot, $language);
                })(),
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See competitors', 'Vezi competitorii', 'Виж конкурентите'),
                        'message' => '',
                        'ui_action' => 'open_artifact_competitor_identification',
                    ),
                ),
                'ui_action' => 'open_artifact_competitor_identification',
            ),
            'competitor_backlinks_sync' => array(
                'module' => 'competitor_backlinks',
                'assistant_message' => (function() use ($project_id, $setup_user_id, $language) {
                    $snapshot = $this->aysa_adjustments_run_competitor_backlinks_sync($project_id, $setup_user_id);
                    return $this->aysa_adjustments_build_competitor_backlinks_completion_message($snapshot, $language);
                })(),
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See backlinks', 'Vezi backlinks', 'Виж backlinks'),
                        'message' => '',
                        'ui_action' => 'open_artifact_competitor_backlinks',
                    ),
                ),
                'ui_action' => 'open_artifact_competitor_backlinks',
            ),
            'offpage_backlink_audit_refresh' => array(
                'module' => 'offpage_backlink_audit',
                'assistant_message' => (function() use ($project_id, $language) {
                    $target = $this->aysa_monitoring_extract_host((string) home_url('/'));
                    $this->aysa_adjustments_run_offpage_backlink_audit($project_id, $target, 100, true);
                    $snapshot = $this->aysa_adjustments_get_offpage_backlink_audit_snapshot($project_id);
                    return $this->aysa_adjustments_build_offpage_backlink_audit_completion_message($snapshot, $language);
                })(),
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See backlink audit', 'Vezi auditul backlinkurilor', 'Виж backlink audit-а'),
                        'message' => '',
                        'ui_action' => 'open_artifact_offpage_backlink_audit',
                    ),
                ),
                'ui_action' => 'open_artifact_offpage_backlink_audit',
            ),
            'offpage_link_building_refresh' => array(
                'module' => 'offpage_link_building',
                'assistant_message' => (function() use ($project_id, $language) {
                    $result = $this->aysa_fetch_adverlink_link_issues($project_id);
                    if (!is_wp_error($result)) {
                        $items = (is_array($result) && isset($result['items']) && is_array($result['items'])) ? $result['items'] : array();
                        $this->aysa_save_adverlink_link_issues($project_id, $items, 'manual');
                    }
                    $snapshot = $this->aysa_adjustments_get_offpage_link_building_snapshot($project_id);
                    $pending = intval($snapshot['pending_orders'] ?? 0);
                    $completed = intval($snapshot['completed_orders'] ?? 0);
                    $issues = intval($snapshot['issues_count'] ?? 0);
                    return $this->aysa_adjustments_tr(
                        $language,
                        'I refreshed the off-page order tracking. Right now I see ' . $pending . ' pending orders, ' . $completed . ' completed placements, and ' . $issues . ' link issues worth checking.',
                        'Am reîmprospătat tracking-ul pentru comenzile off-page. Acum văd ' . $pending . ' comenzi în lucru, ' . $completed . ' plasări finalizate și ' . $issues . ' probleme de linkuri care merită verificate.',
                        'Опресних tracking-а за off-page поръчките. В момента виждам ' . $pending . ' чакащи поръчки, ' . $completed . ' завършени публикации и ' . $issues . ' link проблема, които си струва да бъдат проверени.'
                    );
                })(),
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See orders', 'Vezi comenzile', 'Виж поръчките'),
                        'message' => '',
                        'ui_action' => 'open_artifact_offpage_link_building_orders',
                    ),
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See link issues', 'Vezi problemele de linkuri', 'Виж проблемите с линковете'),
                        'message' => '',
                        'ui_action' => 'open_artifact_offpage_link_building_issues',
                    ),
                ),
                'ui_action' => 'open_artifact_offpage_link_building_orders',
            ),
            'missing_pages_refresh' => array(
                'module' => 'missing_pages',
                'assistant_message' => (function() use ($project_id, $language) {
                    $snapshot = $this->aysa_adjustments_get_missing_pages_snapshot($project_id);
                    return $this->aysa_adjustments_build_missing_pages_completion_message($snapshot, $language);
                })(),
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See missing pages', 'Vezi paginile lipsă', 'Виж липсващите страници'),
                        'message' => '',
                        'ui_action' => 'open_artifact_missing_pages',
                    ),
                ),
                'ui_action' => 'open_artifact_missing_pages',
            ),
            'missing_pages_send_to_plan' => array(
                'module' => 'missing_pages',
                'assistant_message' => (function() use ($project_id, $language) {
                    $snapshot = $this->aysa_adjustments_get_missing_pages_snapshot($project_id);
                    $items = isset($snapshot['rows']) && is_array($snapshot['rows']) ? array_values($snapshot['rows']) : array();
                    $result = $this->aysa_missing_pages_send_items_to_plan($project_id, $items);
                    $added = intval($result['added'] ?? 0);
                    $existing = intval($result['existing'] ?? 0);
                    $errors = intval($result['errors'] ?? 0);

                    return $this->aysa_adjustments_tr(
                        $language,
                        'I moved the missing page ideas into the content plan. I added ' . $added . ' new planning items, ' . $existing . ' were already there, and ' . $errors . ' could not be saved.',
                        'Am trimis ideile de pagini lipsă în planul de conținut. Am adăugat ' . $added . ' itemi noi în plan, ' . $existing . ' existau deja, iar ' . $errors . ' nu au putut fi salvate.',
                        'Изпратих идеите за липсващи страници към content плана. Добавих ' . $added . ' нови елемента в плана, ' . $existing . ' вече съществуваха, а ' . $errors . ' не можаха да бъдат запазени.'
                    );
                })(),
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See content plan', 'Vezi planul de conținut', 'Виж content плана'),
                        'message' => '',
                        'ui_action' => 'open_artifact_content_strategy',
                    ),
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See missing pages', 'Vezi paginile lipsă', 'Виж липсващите страници'),
                        'message' => '',
                        'ui_action' => 'open_artifact_missing_pages',
                    ),
                ),
                'ui_action' => 'open_artifact_content_strategy',
            ),
            'content_strategy_refresh' => array(
                'module' => 'content_strategy',
                'assistant_message' => (function() use ($project_id, $language) {
                    $snapshot = $this->aysa_adjustments_get_content_strategy_snapshot($project_id);
                    return $this->aysa_adjustments_build_content_strategy_completion_message($snapshot, $language);
                })(),
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See content strategy', 'Vezi content strategy', 'Виж content strategy'),
                        'message' => '',
                        'ui_action' => 'open_artifact_content_strategy',
                    ),
                ),
                'ui_action' => 'open_artifact_content_strategy',
            ),
            'technical_site_audit_start' => array(
                'module' => 'technical_site_audit',
                'assistant_message' => (function() use ($project_id, $language) {
                    $snapshot = $this->aysa_adjustments_run_technical_site_audit_refresh($project_id, true);
                    return $this->aysa_adjustments_build_technical_site_audit_completion_message($snapshot, $language, true);
                })(),
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See site audit', 'Vezi auditul tehnic', 'Виж site audit-а'),
                        'message' => '',
                        'ui_action' => 'open_artifact_technical_site_audit',
                    ),
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See issues', 'Vezi problemele', 'Виж проблемите'),
                        'message' => '',
                        'ui_action' => 'open_artifact_technical_site_audit_issues',
                    ),
                ),
            ),
            'technical_site_audit_refresh' => array(
                'module' => 'technical_site_audit',
                'assistant_message' => (function() use ($project_id, $language) {
                    $snapshot = $this->aysa_adjustments_run_technical_site_audit_refresh($project_id, false);
                    return $this->aysa_adjustments_build_technical_site_audit_completion_message($snapshot, $language, false);
                })(),
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See site audit', 'Vezi auditul tehnic', 'Виж site audit-а'),
                        'message' => '',
                        'ui_action' => 'open_artifact_technical_site_audit',
                    ),
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See crawled pages', 'Vezi paginile crawl-ate', 'Виж обходените страници'),
                        'message' => '',
                        'ui_action' => 'open_artifact_technical_site_audit_pages',
                    ),
                ),
            ),
            'technical_pagespeed_refresh' => array(
                'module' => 'technical_pagespeed',
                'assistant_message' => (function() use ($project_id, $language) {
                    $snapshot = $this->aysa_adjustments_run_technical_pagespeed_refresh($project_id, true);
                    return $this->aysa_adjustments_build_technical_pagespeed_completion_message($snapshot, $language);
                })(),
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See PageSpeed', 'Vezi PageSpeed', 'Виж PageSpeed'),
                        'message' => '',
                        'ui_action' => 'open_artifact_technical_pagespeed',
                    ),
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See opportunities', 'Vezi oportunitățile', 'Виж възможностите'),
                        'message' => '',
                        'ui_action' => 'open_artifact_technical_pagespeed_opportunities',
                    ),
                ),
                'ui_action' => 'open_artifact_technical_pagespeed',
            ),
            'technical_sitemap_robots_refresh' => array(
                'module' => 'technical_sitemap_robots',
                'assistant_message' => (function() use ($project_id, $language) {
                    $snapshot = $this->aysa_adjustments_run_technical_sitemap_robots_refresh($project_id, true);
                    return $this->aysa_adjustments_build_technical_sitemap_robots_completion_message($snapshot, $language);
                })(),
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See checks', 'Vezi verificările', 'Виж проверките'),
                        'message' => '',
                        'ui_action' => 'open_artifact_technical_sitemap_robots_checks',
                    ),
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See robots.txt', 'Vezi robots.txt', 'Виж robots.txt'),
                        'message' => '',
                        'ui_action' => 'open_artifact_technical_sitemap_robots_file',
                    ),
                ),
                'ui_action' => 'open_artifact_technical_sitemap_robots',
            ),
            'technical_internal_linking_preview' => array(
                'module' => 'technical_internal_linking',
                'assistant_message' => (function() use ($project_id, $language) {
                    $result = $this->aysa_adjustments_run_technical_internal_linking_preview($project_id);
                    return $this->aysa_adjustments_build_technical_internal_linking_preview_message($result, $language);
                })(),
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See internal linking opportunities', 'Vezi oportunitățile de linkuri interne', 'Виж възможностите за вътрешни линкове'),
                        'message' => '',
                        'ui_action' => 'open_artifact_technical_internal_linking_preview',
                    ),
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'Apply internal links', 'Aplică linkurile interne', 'Приложи вътрешните линкове'),
                        'message' => $this->aysa_adjustments_tr($language, 'Apply the internal links now.', 'Aplică linkurile interne acum.', 'Приложи вътрешните линкове сега.'),
                    ),
                ),
                'ui_action' => 'open_artifact_technical_internal_linking_preview',
            ),
            'technical_internal_linking_process' => array(
                'module' => 'technical_internal_linking',
                'assistant_message' => (function() use ($project_id, $language) {
                    $result = $this->aysa_adjustments_run_technical_internal_linking_process($project_id);
                    return $this->aysa_adjustments_build_technical_internal_linking_process_message($result, $language);
                })(),
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See internal linking', 'Vezi linkurile interne', 'Виж вътрешните линкове'),
                        'message' => '',
                        'ui_action' => 'open_artifact_technical_internal_linking',
                    ),
                ),
                'ui_action' => 'open_artifact_technical_internal_linking',
            ),
            'onpage_optimize_needed' => array(
                'module' => 'onpage_optimization',
                'assistant_message' => (function() use ($project_id, $language) {
                    $result = $this->aysa_adjustments_run_onpage_optimize_needed($project_id);
                    return $this->aysa_adjustments_build_onpage_optimize_needed_message($result, $language);
                })(),
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See on-page pages', 'Vezi paginile on-page', 'Виж on-page страниците'),
                        'message' => '',
                        'ui_action' => 'open_artifact_onpage_optimization',
                    ),
                ),
                'ui_action' => 'open_artifact_onpage_optimization',
            ),
            'technical_robots_unblock' => array(
                'module' => 'technical_sitemap_robots',
                'assistant_message' => (function() use ($project_id, $language) {
                    $target_url = $this->aysa_technical_resolve_target_url();
                    $sitemaps = $this->aysa_technical_fetch_sitemap_candidates($target_url);
                    $primary_sitemap = '';
                    foreach ($sitemaps as $row) {
                        if (!empty($row['reachable']) && !empty($row['url'])) {
                            $primary_sitemap = (string) $row['url'];
                            break;
                        }
                    }
                    $content = $this->aysa_technical_robots_default_content($target_url, $primary_sitemap);
                    $saved = $this->aysa_technical_save_robots_content($content);
                    if (is_wp_error($saved)) {
                        return $this->aysa_adjustments_tr(
                            $language,
                            'I could not update robots.txt automatically. Please check file permissions in WordPress root and try again.',
                            'Nu am putut actualiza automat robots.txt. Verifică permisiunile fișierului din rădăcina WordPress și încearcă din nou.',
                            'Не успях да обновя robots.txt автоматично. Провери правата на файла в WordPress root и опитай отново.'
                        );
                    }
                    $audit = $this->aysa_technical_build_sitemap_robots_audit($target_url);
                    $state = array(
                        'project_id' => $project_id,
                        'updated_at' => current_time('mysql'),
                        'updated_at_unix' => time(),
                        'data' => $audit,
                    );
                    update_option($this->aysa_technical_sitemap_robots_state_key($project_id), $state, false);
                    $this->aysa_wallet_track_action_usage(
                        $project_id,
                        'aysa_technical_robots_restore_default',
                        'technical_robots_unblock',
                        array(
                            'module' => 'technical_optimization',
                            'target_url' => $target_url,
                            'source' => 'adjustments_agent',
                        ),
                        12,
                        '/v1/technical/robots/restore-default'
                    );
                    return $this->aysa_adjustments_tr(
                        $language,
                        'I updated robots.txt so search engines can crawl the site again. I also refreshed the sitemap and robots checks for you.',
                        'Am actualizat robots.txt astfel încât motoarele de căutare să poată crawl-a din nou site-ul. Am reîmprospătat și verificările pentru sitemap și robots.',
                        'Обнових robots.txt така, че търсачките отново да могат да обхождат сайта. Опресних и проверките за sitemap и robots.'
                    );
                })(),
                'suggested_actions' => array(
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See checks', 'Vezi verificările', 'Виж проверките'),
                        'message' => '',
                        'ui_action' => 'open_artifact_technical_sitemap_robots_checks',
                    ),
                    array(
                        'label' => $this->aysa_adjustments_tr($language, 'See robots.txt', 'Vezi robots.txt', 'Виж robots.txt'),
                        'message' => '',
                        'ui_action' => 'open_artifact_technical_sitemap_robots_file',
                    ),
                ),
                'ui_action' => 'open_artifact_technical_sitemap_robots',
            ),
            default => array(),
        };
    }

    private function aysa_adjustments_run_content_strategy_generation($project_id, $setup_user_id, $strategy_type, $custom_payload, $language) {
        $project_id = intval($project_id);
        $setup_user_id = intval($setup_user_id);
        $strategy_type = sanitize_key((string) $strategy_type);
        $custom_payload = is_array($custom_payload) ? $custom_payload : array();

        if ($strategy_type === '') {
            $strategy_type = 'local_seo';
        }

        $context_bundle = $this->aysa_collect_content_strategy_context_bundle($project_id, $setup_user_id);
        $result = $this->aysa_broker_request('POST', '/v1/content-strategy/generate', array(
            'project_id' => $project_id,
            'strategy_type' => $strategy_type,
            'raw_context' => $context_bundle,
            'payload_overrides' => $custom_payload,
        ), 120);
        if (is_wp_error($result)) {
            return array(
                'module' => 'content_strategy',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I could not generate the content plan right now. We can try again in a moment.',
                    'Nu am reușit să generez planul de conținut acum. Putem încerca din nou imediat.',
                    'Не успях да генерирам content плана точно сега. Можем да опитаме отново след малко.'
                ),
            );
        }

        $items = isset($result['items']) && is_array($result['items']) ? $result['items'] : array();
        $save_payload = array(
            'strategy_type' => (string) ($result['strategy_type'] ?? $strategy_type),
            'model' => (string) ($result['model'] ?? ''),
            'usage' => (isset($result['usage']) && is_array($result['usage'])) ? $result['usage'] : array(),
            'warnings' => (isset($result['warnings']) && is_array($result['warnings'])) ? $result['warnings'] : array(),
            'source' => 'api_thin_client',
        );
        $save_result = $this->aysa_adjustments_save_content_strategy_items($project_id, $strategy_type, $items, $save_payload);
        $snapshot = $this->aysa_adjustments_get_content_strategy_snapshot($project_id);
        $saved = intval($save_result['saved'] ?? 0);
        $existing = intval($save_result['existing'] ?? 0);
        $errors = intval($save_result['errors'] ?? 0);
        $saved_items = isset($save_result['saved_items']) && is_array($save_result['saved_items']) ? $save_result['saved_items'] : array();
        $existing_items = isset($save_result['existing_items']) && is_array($save_result['existing_items']) ? $save_result['existing_items'] : array();
        $preview_lines = $this->aysa_adjustments_build_content_strategy_preview_lines(!empty($saved_items) ? $saved_items : $existing_items, $language, 5);
        $plan_batch = sanitize_text_field((string) ($save_result['plan_batch'] ?? ''));

        return array(
            'module' => 'content_strategy',
            'assistant_message' => $this->aysa_adjustments_build_content_strategy_generation_message(
                $language,
                $strategy_type,
                $saved,
                $existing,
                $errors,
                intval($snapshot['total'] ?? 0),
                intval($snapshot['published'] ?? 0),
                $preview_lines,
                !empty($saved_items) ? 'saved' : (!empty($existing_items) ? 'existing' : '')
            ),
            'suggested_actions' => array(
                array(
                    'label' => $this->aysa_adjustments_tr($language, 'See content plan', 'Vezi planul de conținut', 'Виж content плана'),
                    'message' => '',
                    'ui_action' => 'open_artifact_content_strategy',
                ),
                array(
                    'label' => $this->aysa_adjustments_tr($language, 'Delete this batch', 'Șterge ideile generate acum', 'Изтрий генерираните идеи сега'),
                    'message' => $this->aysa_adjustments_tr($language, 'Delete the ideas generated just now.', 'Șterge ideile generate acum.', 'Изтрий идеите, генерирани току-що.'),
                ),
            ),
            'state_meta' => array(
                'last_generated_content_plan_batch' => $plan_batch,
                'last_generated_content_strategy_type' => $strategy_type,
            ),
        );
    }

    private function aysa_adjustments_apply_preview_changes_to_pending_reviews($project_id, array $payload, $language) {
        global $wpdb;

        $pages = isset($payload['pages']) && is_array($payload['pages']) ? $payload['pages'] : array();
        if (empty($pages)) {
            return array(
                'module' => 'onpage_optimization',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I did not find any approved preview rows to send into review.',
                    'Nu am găsit niciun rând aprobat din preview pe care să-l trimit în review.',
                    'Не открих одобрени preview редове, които да изпратя за review.'
                ),
            );
        }

        $table_name = esc_sql($wpdb->prefix . 'aysa_automated_pending_reviews');
        $created = 0;
        $existing = 0;
        $failed = 0;
        $titles = array();

        foreach ($pages as $page) {
            if (!is_array($page)) {
                continue;
            }

            $entity_id = intval($page['entity_id'] ?? 0);
            $entity_type = $this->aysa_adjustments_normalize_preview_entity_type((string) ($page['entity_type'] ?? ''));
            $keyword = sanitize_text_field((string) ($page['keyword'] ?? ''));
            $title = sanitize_text_field((string) ($page['title'] ?? ''));
            $fields = isset($page['fields']) && is_array($page['fields']) ? $page['fields'] : array();

            if ($entity_id <= 0 || $entity_type === '' || $keyword === '' || empty($fields)) {
                $failed++;
                continue;
            }

            if ($this->aysa_adjustments_has_pending_seo_review($entity_id, $entity_type, $keyword)) {
                $existing++;
                continue;
            }

            $meta_title = '';
            $meta_description = '';
            $heading_h1 = '';
            foreach ($fields as $field) {
                if (!is_array($field)) {
                    continue;
                }
                $field_name = sanitize_key((string) ($field['field'] ?? ''));
                $proposed = isset($field['proposed']) ? sanitize_textarea_field((string) $field['proposed']) : '';
                if ($field_name === 'seo_title') {
                    $meta_title = sanitize_text_field($proposed);
                } elseif ($field_name === 'meta_description') {
                    $meta_description = sanitize_textarea_field($proposed);
                } elseif ($field_name === 'heading_h1') {
                    $heading_h1 = sanitize_text_field($proposed);
                }
            }

            if ($meta_title === '' && $meta_description === '' && $heading_h1 === '') {
                $failed++;
                continue;
            }

            $seo_data = array(
                'metaTitle' => $meta_title,
                'metaDescription' => $meta_description,
                'metaKeywords' => $keyword,
                'proposedHeadingH1' => $heading_h1,
                'previewSource' => 'adjustments_preview_v2',
                'previewTitle' => $title,
            );

            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $inserted = $wpdb->insert(
                $table_name,
                array(
                    'entity_id' => $entity_id,
                    'entity_type' => $entity_type,
                    'keyword' => $keyword,
                    'seo_data' => wp_json_encode($seo_data),
                    'project_id' => intval($project_id),
                    'status' => 'pending',
                    'created_at' => current_time('mysql'),
                    'source' => 'adjustments_preview_v2',
                ),
                array('%d', '%s', '%s', '%s', '%d', '%s', '%s', '%s')
            );

            if ($inserted === false) {
                $failed++;
                continue;
            }

            $created++;
            if ($title !== '') {
                $titles[] = $title;
            }
        }

        $examples = array_slice($titles, 0, 3);
        $examples_text = !empty($examples) ? implode(', ', $examples) : '';
        $assistant_message = match ($language) {
            'ro' => 'Am trimis ' . $created . ' pagini în pending review pe baza preview-ului aprobat.'
                . ($existing > 0 ? ' ' . $existing . ' erau deja în review.' : '')
                . ($failed > 0 ? ' ' . $failed . ' nu au putut fi adăugate.' : '')
                . ($examples_text !== '' ? "\n\nPrimele exemple: " . $examples_text . '.' : '')
                . "\n\nAm pus în review titlurile SEO și meta description-urile propuse. Heading-urile rămân vizibile în preview pentru verificare manuală înainte de orice aplicare live.",
            'bg' => 'Изпратих ' . $created . ' страници в pending review според одобрения preview.'
                . ($existing > 0 ? ' ' . $existing . ' вече бяха в review.' : '')
                . ($failed > 0 ? ' ' . $failed . ' не можаха да бъдат добавени.' : '')
                . ($examples_text !== '' ? "\n\nПърви примери: " . $examples_text . '.' : '')
                . "\n\nИзпратих за review предложените SEO title-и и meta description-и. Heading-ите остават видими в preview за ръчна проверка преди live прилагане.",
            default => 'I sent ' . $created . ' pages into pending review from the approved preview.'
                . ($existing > 0 ? ' ' . $existing . ' were already pending review.' : '')
                . ($failed > 0 ? ' ' . $failed . ' could not be queued.' : '')
                . ($examples_text !== '' ? "\n\nFirst examples: " . $examples_text . '.' : '')
                . "\n\nI queued the proposed SEO titles and meta descriptions for review. The H1 suggestions stay visible in preview for manual verification before any live apply.",
        };

        return array(
            'module' => 'onpage_optimization',
            'assistant_message' => $assistant_message,
            'suggested_actions' => array(
                array(
                    'label' => $this->aysa_adjustments_tr($language, 'See on-page pages', 'Vezi paginile on-page', 'Виж on-page страниците'),
                    'message' => '',
                    'ui_action' => 'open_artifact_onpage_optimization',
                ),
            ),
            'ui_action' => 'open_artifact_onpage_optimization',
            'state_meta' => array(
                'active_research_module' => 'onpage_optimization',
            ),
        );
    }

    private function aysa_adjustments_apply_redirect_preview(array $payload, $language) {
        $items = isset($payload['items']) && is_array($payload['items']) ? array_values($payload['items']) : array();
        if ($items === array()) {
            return array(
                'module' => 'technical_404_errors',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I did not receive any redirect candidates to apply.',
                    'Nu am primit niciun redirect candidat de aplicat.',
                    'Не получих никакви кандидати за redirect, които да приложа.'
                ),
            );
        }

        $applied = 0;
        $duplicates = 0;
        $skipped = 0;
        $errors = array();

        foreach ($items as $item) {
            if (!is_array($item)) {
                $skipped++;
                continue;
            }

            $source = isset($item['source_url']) ? (string) $item['source_url'] : '';
            $destination = isset($item['destination_url']) ? (string) $item['destination_url'] : '';
            if (trim($source) === '' || trim($destination) === '') {
                $skipped++;
                continue;
            }

            $result = $this->aysa_add_htaccess_redirect_rule($source, $destination);
            if (!empty($result['ok'])) {
                $applied++;
                continue;
            }

            $message = (string) ($result['message'] ?? '');
            if (stripos($message, 'already exists') !== false) {
                $duplicates++;
            } else {
                $errors[] = $message !== '' ? $message : 'unknown_error';
            }
        }

        $assistant_message = match ($language) {
            'ro' => 'Am adăugat ' . $applied . ' redirecturi 301 pentru URL-urile 404 cu destinație clară.'
                . ($duplicates > 0 ? ' ' . $duplicates . ' reguli existau deja.' : '')
                . ($skipped > 0 ? ' Am sărit peste ' . $skipped . ' rânduri incomplete.' : '')
                . (!empty($errors) ? ' Au rămas și ' . count($errors) . ' erori de aplicare care cer verificare manuală.' : ''),
            'bg' => 'Добавих ' . $applied . ' 301 redirect-а за 404 URL-ите с ясна дестинация.'
                . ($duplicates > 0 ? ' ' . $duplicates . ' правила вече съществуваха.' : '')
                . ($skipped > 0 ? ' Пропуснах ' . $skipped . ' непълни реда.' : '')
                . (!empty($errors) ? ' Останаха и ' . count($errors) . ' грешки за ръчна проверка.' : ''),
            default => 'I added ' . $applied . ' 301 redirects for the 404 URLs that already had clear destinations.'
                . ($duplicates > 0 ? ' ' . $duplicates . ' rules already existed.' : '')
                . ($skipped > 0 ? ' I skipped ' . $skipped . ' incomplete rows.' : '')
                . (!empty($errors) ? ' There are also ' . count($errors) . ' apply errors that still need manual review.' : ''),
        };

        return array(
            'module' => 'technical_404_errors',
            'assistant_message' => $assistant_message,
            'suggested_actions' => array(
                array(
                    'label' => $this->aysa_adjustments_tr($language, 'See 301 redirects', 'Vezi redirecturile 301', 'Виж 301 redirect-ите'),
                    'message' => '',
                    'ui_action' => 'open_artifact_technical_301_redirects',
                ),
                array(
                    'label' => $this->aysa_adjustments_tr($language, 'See remaining 404s', 'Vezi 404-urile rămase', 'Виж оставащите 404'),
                    'message' => '',
                    'ui_action' => 'open_artifact_technical_404_errors',
                ),
            ),
            'ui_action' => 'open_artifact_technical_301_redirects',
            'state_meta' => array(
                'active_research_module' => 'technical_404_errors',
            ),
        );
    }

    private function aysa_normalize_redirect_source($source) {
        $source = trim((string) $source);
        if ($source === '') {
            return '';
        }

        if (preg_match('#^https?://#i', $source) === 1) {
            $path = (string) wp_parse_url($source, PHP_URL_PATH);
            $query = (string) wp_parse_url($source, PHP_URL_QUERY);
            $source = $path !== '' ? $path : '/';
            if ($query !== '') {
                $source .= '?' . $query;
            }
        }

        if (strpos($source, '/') !== 0) {
            $source = '/' . ltrim($source, '/');
        }

        return $source;
    }

    private function aysa_normalize_redirect_destination($destination) {
        $destination = trim((string) $destination);
        if ($destination === '') {
            return '';
        }

        if (preg_match('#^https?://#i', $destination) === 1) {
            $host = (string) wp_parse_url($destination, PHP_URL_HOST);
            $site_host = (string) wp_parse_url(home_url('/'), PHP_URL_HOST);
            if ($host !== '' && $site_host !== '' && strcasecmp($host, $site_host) === 0) {
                $path = (string) wp_parse_url($destination, PHP_URL_PATH);
                $query = (string) wp_parse_url($destination, PHP_URL_QUERY);
                $destination = $path !== '' ? $path : '/';
                if ($query !== '') {
                    $destination .= '?' . $query;
                }
            } else {
                return $destination;
            }
        }

        if (strpos($destination, '/') !== 0) {
            $destination = '/' . ltrim($destination, '/');
        }

        return $destination;
    }

    private function aysa_add_htaccess_redirect_rule($source, $destination) {
        $source = $this->aysa_normalize_redirect_source($source);
        $destination = $this->aysa_normalize_redirect_destination($destination);

        if ($source === '' || $destination === '') {
            return array(
                'ok' => false,
                'message' => 'Source or destination URL is missing.',
            );
        }

        $htaccess_file = ABSPATH . '.htaccess';
        if (!file_exists($htaccess_file)) {
            return array(
                'ok' => false,
                'message' => 'The .htaccess file does not exist in the WordPress root directory.',
            );
        }

        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- Admin-authorized WordPress-root file operations retain a fail-closed writability preflight before the guarded write.
        if (!is_writable($htaccess_file)) {
            return array(
                'ok' => false,
                'message' => 'The .htaccess file is not writable. Please check file permissions.',
            );
        }

        $htaccess_content = file_get_contents($htaccess_file);
        if (!is_string($htaccess_content)) {
            return array(
                'ok' => false,
                'message' => 'Failed to read the .htaccess file.',
            );
        }

        $aysa_section_start = '# BEGIN Aysa Redirects';
        $aysa_section_end = '# END Aysa Redirects';
        $redirect_rule = 'RedirectMatch 301 ^' . preg_quote($source, '/') . '$ ' . $destination;

        if (strpos($htaccess_content, $aysa_section_start) !== false) {
            $pattern = '/' . preg_quote($aysa_section_start, '/') . '(.*?)' . preg_quote($aysa_section_end, '/') . '/s';
            if (preg_match($pattern, $htaccess_content, $matches)) {
                $existing_content = isset($matches[1]) ? (string) $matches[1] : '';
                if (strpos($existing_content, $redirect_rule) !== false) {
                    return array(
                        'ok' => true,
                        'already_done' => true,
                        'code' => 'redirect_rule_exists',
                        'message' => 'This redirect already exists in .htaccess.',
                    );
                }

                $new_content = $aysa_section_start . "\n" . $existing_content . $redirect_rule . "\n" . $aysa_section_end;
                $htaccess_content = (string) preg_replace($pattern, $new_content, $htaccess_content);
            }
        } else {
            $new_section = $aysa_section_start . "\n" . $redirect_rule . "\n" . $aysa_section_end . "\n\n";
            $htaccess_content = $new_section . $htaccess_content;
        }

        // phpcs:ignore PluginCheck.CodeAnalysis.WriteFile.ABSPATHDetected -- Admin-authorized redirect operation intentionally updates site-root .htaccess after nonce, capability, path and writability checks.
        $result = file_put_contents($htaccess_file, $htaccess_content);
        if ($result === false) {
            return array(
                'ok' => false,
                'message' => 'Failed to write to .htaccess file.',
            );
        }

        return array(
            'ok' => true,
            'message' => 'Redirect added successfully to .htaccess file.',
            'redirect_rule' => $redirect_rule,
        );
    }

    private function aysa_adjustments_normalize_preview_entity_type($value) {
        $value = trim((string) $value);
        return match (strtolower($value)) {
            'product' => 'Product',
            'category' => 'Category',
            'page' => 'Page',
            'post' => 'Post',
            default => in_array($value, array('Product', 'Category', 'Page', 'Post'), true) ? $value : '',
        };
    }

    private function aysa_adjustments_recommend_seo_text_ads_portals($project_id, $setup_user_id, $language) {
        $summary = $this->aysa_get_local_setup_state_for_user($setup_user_id);
        $general = isset($summary['general']) && is_array($summary['general']) ? $summary['general'] : array();
        $competition = isset($summary['competition']) && is_array($summary['competition']) ? $summary['competition'] : array();
        $seed_keyword = '';
        for ($i = 1; $i <= 5; $i++) {
            $candidate = trim((string) ($competition['keyword' . $i] ?? ''));
            if ($candidate !== '') {
                $seed_keyword = $candidate;
                break;
            }
        }
        if ($seed_keyword === '') {
            $seed_keyword = sanitize_text_field((string) ($general['industry'] ?? ''));
        }

        $portal_result = $this->aysa_adjustments_list_adverlink_portals_internal($project_id, 'seo_ads_text', 'monthly', 50, true);
        if (is_wp_error($portal_result)) {
            return array(
                'module' => 'offpage_link_building',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I could not load SEO Text Ads websites right now. We can try again in a moment.',
                    'Nu am reușit să încarc acum website-urile pentru SEO Text Ads. Putem încerca din nou imediat.',
                    'Не успях да заредя сайтовете за SEO Text Ads точно сега. Можем да опитаме отново след малко.'
                ),
            );
        }

        $items = isset($portal_result['items']) && is_array($portal_result['items']) ? $portal_result['items'] : array();
        $shortlist = $this->aysa_adjustments_build_seo_text_ads_shortlist($items, $summary);
        if (empty($shortlist)) {
            return array(
                'module' => 'offpage_link_building',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I did not find suitable SEO Text Ads websites right now. We can refresh the marketplace and try again.',
                    'Nu am găsit acum website-uri potrivite pentru SEO Text Ads. Putem reîmprospăta marketplace-ul și încerca din nou.',
                    'В момента не намерих подходящи сайтове за SEO Text Ads. Можем да опресним marketplace-а и да опитаме отново.'
                ),
            );
        }

        $preview_lines = array();
        $suggested_actions = array();
        foreach ($shortlist as $index => $portal) {
            $domain = (string) ($portal['domain'] ?? '');
            $authority = (float) ($portal['authority'] ?? 0);
            $monthly_cost = intval($portal['monthly_cost_cents'] ?? 0);
            $preview_lines[] = ($index + 1) . '. ' . $domain
                . ' | ' . $this->aysa_adjustments_tr($language, 'authority', 'autoritate', 'authority') . ': ' . number_format($authority, 1)
                . ' | ' . $this->aysa_adjustments_tr($language, 'monthly cost', 'cost lunar', 'месечна цена') . ': ' . $monthly_cost;
            $suggested_actions[] = array(
                'label' => $domain,
                'message' => $domain,
            );
        }

        $message = match ($language) {
            'ro' => 'Am ales pentru tine 3 website-uri potrivite pentru SEO Text Ads. M-am uitat la autoritate, relevanță și disponibilitatea acestui tip de plasare. '
                . 'Uite opțiunile cele mai bune acum:' . "\n\n- " . implode("\n- ", $preview_lines)
                . "\n\n" . 'Spune-mi pe care îl dorești și îți pregătesc comanda lunară.',
            'bg' => 'Подбрах за теб 3 подходящи сайта за SEO Text Ads. Гледах authority, релевантност и наличност за този тип placement. '
                . 'Ето най-добрите варианти в момента:' . "\n\n- " . implode("\n- ", $preview_lines)
                . "\n\n" . 'Кажи ми кой искаш и ще подготвя месечната поръчка.',
            default => 'I picked 3 suitable websites for SEO Text Ads for you. I looked at authority, relevance, and whether they support this placement type. '
                . 'Here are the best options right now:' . "\n\n- " . implode("\n- ", $preview_lines)
                . "\n\n" . 'Tell me which one you want and I will prepare the monthly order.',
        };

        $suggested_actions[] = array(
            'label' => $this->aysa_adjustments_tr($language, 'Refresh options', 'Reîmprospătează opțiunile', 'Опресни опциите'),
            'message' => $this->aysa_adjustments_tr($language, 'Refresh the SEO Text Ads options.', 'Reîmprospătează opțiunile SEO Text Ads.', 'Опресни опциите за SEO Text Ads.'),
        );

        return array(
            'module' => 'offpage_link_building',
            'assistant_message' => $message,
            'suggested_actions' => $suggested_actions,
            'state_meta' => array(
                'offpage_purchase_flow' => 'seo_text_ads',
                'offpage_purchase_stage' => 'awaiting_selection',
                'offpage_purchase_selection_mode' => 'ai_shortlist',
                'offpage_purchase_candidates' => $shortlist,
                'offpage_purchase_keyword' => $seed_keyword,
            ),
        );
    }

    private function aysa_adjustments_place_seo_text_ads_order($project_id, $setup_user_id, array $payload, $language) {
        $portal_id = sanitize_text_field((string) ($payload['portal_id'] ?? ''));
        $portal_domain = sanitize_text_field((string) ($payload['portal_domain'] ?? ''));
        $keyword = sanitize_text_field((string) ($payload['keyword'] ?? ''));
        $monthly_cost_cents = max(0, intval($payload['monthly_cost_cents'] ?? 0));

        if ($portal_id === '' || $portal_domain === '' || $keyword === '' || $monthly_cost_cents <= 0) {
            return array(
                'module' => 'offpage_link_building',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I am missing a few order details for this SEO Text Ads placement, so I stopped before placing it.',
                    'Îmi lipsesc câteva detalii pentru această comandă SEO Text Ads, așa că m-am oprit înainte să o plasez.',
                    'Липсват ми няколко детайла за тази SEO Text Ads поръчка, затова спрях преди да я пусна.'
                ),
            );
        }

        $setup = $this->aysa_get_local_setup_state_for_user($setup_user_id);
        $general = isset($setup['general']) && is_array($setup['general']) ? $setup['general'] : array();
        $business_name = sanitize_text_field((string) ($general['company_name'] ?? get_bloginfo('name')));
        $target_url = $this->aysa_setup_normalize_url((string) ($general['website'] ?? home_url('/')));
        if ($target_url === '') {
            $target_url = home_url('/');
        }
        $display_host = wp_parse_url($target_url, PHP_URL_HOST);
        $display_url = $display_host ? 'https://' . $display_host : $target_url;
        $title = sanitize_text_field($business_name . ' - ' . $keyword);
        $description = $this->aysa_adjustments_build_seo_text_ads_description($business_name, $keyword);

        $order_result = $this->aysa_adjustments_create_adverlink_order_internal($project_id, array(
            'portal_id' => $portal_id,
            'portal_domain' => $portal_domain,
            'service_type' => 'seo_ads_text',
            'billing_type' => 'monthly',
            'keyword' => $keyword,
            'target_url' => $target_url,
            'title' => $title,
            'ad_display_url' => $display_url,
            'ad_description' => $description,
            'duration_months' => 1,
            'estimated_credits' => $monthly_cost_cents,
            'client_name' => $business_name,
            'project_ref' => 'wp-project-' . intval($project_id),
            'order_meta' => array(
                'source' => 'adjustments_agent',
                'placement_type' => 'seo_text_ads',
            ),
        ));

        if (is_wp_error($order_result)) {
            if ($order_result->get_error_code() === 'insufficient_credits') {
                $error_data = $order_result->get_error_data();
                return $this->aysa_build_insufficient_credits_payment_response(
                    $project_id,
                    intval(is_array($error_data) ? ($error_data['required_credits'] ?? 0) : 0),
                    intval(is_array($error_data) ? ($error_data['available_credits'] ?? 0) : 0),
                    $language,
                    'seo_text_ads_order',
                    'offpage_link_building',
                    array(
                        'portal_id' => $portal_id,
                        'portal_domain' => $portal_domain,
                        'target_url' => $target_url,
                        'action_ref' => 'seo_text_ads_order|' . $portal_id . '|' . $target_url,
                    )
                );
            }
            return array(
                'module' => 'offpage_link_building',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I could not place the SEO Text Ads order right now: ',
                    'Nu am putut plasa acum comanda SEO Text Ads: ',
                    'Не успях да пусна SEO Text Ads поръчката сега: '
                ) . $order_result->get_error_message(),
            );
        }

        $charged = intval($order_result['charged_credits_cents'] ?? 0);
        $included_link_applied = !empty($order_result['included_link_applied']);
        $order_ref = sanitize_text_field((string) ($order_result['order_ref'] ?? ''));
        $message = match ($language) {
            'ro' => 'Am trimis comanda SEO Text Ads pentru ' . $portal_domain . '. '
                . ($included_link_applied
                    ? 'Prima lună a fost acoperită din linkurile incluse în abonament, fără cost suplimentar în credite. '
                    : 'Am consumat acum ' . $charged . ' credite pentru prima lună. ')
                . 'De aici înainte, costul se va deduce lunar din contul tău cât timp păstrăm plasarea activă. '
                . 'Beneficiul tău este dublu: reclamă text clickabilă în articol și transfer de autoritate către site-ul tău.',
            'bg' => 'Изпратих SEO Text Ads поръчката за ' . $portal_domain . '. '
                . ($included_link_applied
                    ? 'Първият месец е покрит от включените линкове в абонамента, без допълнителен разход на кредити. '
                    : 'Сега изразходвах ' . $charged . ' кредита за първия месец. ')
                . 'Оттук нататък сумата ще се удържа месечно от акаунта ти, докато placement-ът е активен. '
                . 'Ползата е двойна: кликаема текстова реклама в статията и прехвърляне на authority към сайта ти.',
            default => 'I placed the SEO Text Ads order for ' . $portal_domain . '. '
                . ($included_link_applied
                    ? 'The first month was covered by your included subscription links, so no extra credits were charged. '
                    : 'I charged ' . $charged . ' credits now for the first month. ')
                . 'From here on, the cost will be deducted monthly from your account while the placement stays active. '
                . 'You get a double benefit: a clickable text ad inside the article and authority passed to your website.',
        };
        if ($order_ref !== '') {
            $message .= "\n\n" . $this->aysa_adjustments_tr($language, 'Order reference', 'Referință comandă', 'Референция на поръчка') . ': ' . $order_ref;
        }

        return array(
            'module' => 'offpage_link_building',
            'assistant_message' => $message,
            'suggested_actions' => array(
                array(
                    'label' => $this->aysa_adjustments_tr($language, 'See link building orders', 'Vezi comenzile de link building', 'Виж поръчките за link building'),
                    'message' => '',
                    'ui_action' => 'open_artifact_offpage_link_building_orders',
                ),
            ),
            'ui_action' => 'open_artifact_offpage_link_building_orders',
            'state_meta' => array(
                'offpage_purchase_flow' => '',
                'offpage_purchase_stage' => '',
                'offpage_purchase_selection_mode' => '',
                'offpage_purchase_candidates' => array(),
                'offpage_purchase_selected_candidate' => array(),
            ),
        );
    }

    private function aysa_adjustments_get_seed_keyword_from_setup(array $summary) {
        $general = isset($summary['general']) && is_array($summary['general']) ? $summary['general'] : array();
        $competition = isset($summary['competition']) && is_array($summary['competition']) ? $summary['competition'] : array();

        for ($i = 1; $i <= 5; $i++) {
            $candidate = trim((string) ($competition['keyword' . $i] ?? ''));
            if ($candidate !== '') {
                return sanitize_text_field($candidate);
            }
        }

        $fallback = trim((string) ($general['industry'] ?? ''));
        return sanitize_text_field($fallback);
    }

    private function aysa_adjustments_extract_portal_service_cost_cents(array $row, $service_type, $billing_type) {
        $service_type = sanitize_key((string) $service_type);
        $billing_type = sanitize_key((string) $billing_type);

        $services = isset($row['services']) && is_array($row['services']) ? $row['services'] : array();
        foreach ($services as $service) {
            if (!is_array($service)) {
                continue;
            }
            $serviceType = sanitize_key((string) ($service['service_type'] ?? ''));
            $billingType = sanitize_key((string) ($service['billing_type'] ?? ''));
            if ($serviceType !== $service_type || $billingType !== $billing_type) {
                continue;
            }
            foreach (array('sale_price_eur', 'purchase_price_eur', 'price_eur') as $priceKey) {
                if (!isset($service[$priceKey]) || $service[$priceKey] === '') {
                    continue;
                }
                if (is_numeric($service[$priceKey])) {
                    $credits = (int) round(((float) $service[$priceKey]) * 100);
                    if ($credits > 0) {
                        return $credits;
                    }
                }
            }
        }

        return 0;
    }

    private function aysa_adjustments_build_portal_shortlist(array $items, array $setup_state, $service_type, $billing_type, $limit = 3) {
        $summary_general = isset($setup_state['general']) && is_array($setup_state['general']) ? $setup_state['general'] : array();
        $summary_competition = isset($setup_state['competition']) && is_array($setup_state['competition']) ? $setup_state['competition'] : array();
        $industry = strtolower(trim((string) ($summary_general['industry'] ?? '')));
        $business = strtolower(trim((string) ($summary_general['company_name'] ?? '')));
        $site_host = strtolower((string) wp_parse_url((string) ($summary_general['website'] ?? home_url('/')), PHP_URL_HOST));
        $seed_keywords = array();
        for ($i = 1; $i <= 5; $i++) {
            $keyword = strtolower(trim((string) ($summary_competition['keyword' . $i] ?? '')));
            if ($keyword !== '') {
                $seed_keywords[] = $keyword;
            }
        }

        $prepared = array();
        foreach ($items as $row) {
            if (!is_array($row)) {
                continue;
            }
            $domain = $this->aysa_adjustments_extract_portal_domain($row);
            if ($domain === '' || ($site_host !== '' && $domain === $site_host)) {
                continue;
            }
            $authority = $this->aysa_adjustments_extract_portal_authority($row);
            if ($authority < 10) {
                continue;
            }
            $cost_cents = $this->aysa_adjustments_extract_portal_service_cost_cents($row, $service_type, $billing_type);
            if ($cost_cents <= 0) {
                continue;
            }

            $score = $authority;
            $haystack = strtolower(wp_json_encode($row));
            if ($industry !== '' && str_contains($haystack, $industry)) {
                $score += 4;
            }
            foreach ($seed_keywords as $seed) {
                if ($seed !== '' && str_contains($haystack, $seed)) {
                    $score += 2;
                }
            }
            if ($business !== '' && str_contains($haystack, $business)) {
                $score -= 3;
            }

            $prepared[] = array(
                'portal_id' => sanitize_text_field((string) ($row['portal_id'] ?? $row['id'] ?? '')),
                'domain' => $domain,
                'authority' => $authority,
                'estimated_cost_cents' => $cost_cents,
                'currency' => sanitize_text_field((string) ($row['currency'] ?? 'credits')),
                'score' => $score,
            );
        }

        usort($prepared, static function(array $a, array $b): int {
            $scoreCompare = (float) ($b['score'] ?? 0) <=> (float) ($a['score'] ?? 0);
            if ($scoreCompare !== 0) {
                return $scoreCompare;
            }
            return intval($a['estimated_cost_cents'] ?? 0) <=> intval($b['estimated_cost_cents'] ?? 0);
        });

        $deduped = array();
        $seen = array();
        foreach ($prepared as $portal) {
            $key = strtolower((string) ($portal['domain'] ?? ''));
            if ($key === '' || isset($seen[$key])) {
                continue;
            }
            $seen[$key] = true;
            $deduped[] = $portal;
            if (count($deduped) >= max(1, intval($limit))) {
                break;
            }
        }

        return $deduped;
    }

    private function aysa_adjustments_recommend_link_insertion_portals($project_id, $setup_user_id, $language) {
        $summary = $this->aysa_get_local_setup_state_for_user($setup_user_id);
        $seed_keyword = $this->aysa_adjustments_get_seed_keyword_from_setup($summary);

        $portal_result = $this->aysa_adjustments_list_adverlink_portals_internal($project_id, 'link_insertion', 'one_time', 50, true);
        if (is_wp_error($portal_result)) {
            return array(
                'module' => 'offpage_link_building',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I could not load link insertion websites right now. We can try again in a moment.',
                    'Nu am reușit să încarc acum website-urile pentru link insertion. Putem încerca din nou imediat.',
                    'Не успях да заредя сайтовете за link insertion точно сега. Можем да опитаме отново след малко.'
                ),
            );
        }

        $items = isset($portal_result['items']) && is_array($portal_result['items']) ? $portal_result['items'] : array();
        $shortlist = $this->aysa_adjustments_build_portal_shortlist($items, $summary, 'link_insertion', 'one_time', 3);
        if (empty($shortlist)) {
            return array(
                'module' => 'offpage_link_building',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I did not find suitable link insertion websites right now. We can refresh the marketplace and try again.',
                    'Nu am găsit acum website-uri potrivite pentru link insertion. Putem reîmprospăta marketplace-ul și încerca din nou.',
                    'В момента не намерих подходящи сайтове за link insertion. Можем да опресним marketplace-а и да опитаме отново.'
                ),
            );
        }

        $preview_lines = array();
        $suggested_actions = array();
        foreach ($shortlist as $index => $portal) {
            $domain = (string) ($portal['domain'] ?? '');
            $authority = (float) ($portal['authority'] ?? 0);
            $cost = intval($portal['estimated_cost_cents'] ?? 0);
            $preview_lines[] = ($index + 1) . '. ' . $domain
                . ' | ' . $this->aysa_adjustments_tr($language, 'authority', 'autoritate', 'authority') . ': ' . number_format($authority, 1)
                . ' | ' . $this->aysa_adjustments_tr($language, 'one-time cost', 'cost unic', 'еднократна цена') . ': ' . $cost;
            $suggested_actions[] = array(
                'label' => $domain,
                'message' => $domain,
            );
        }

        return array(
            'module' => 'offpage_link_building',
            'assistant_message' => match ($language) {
                'ro' => 'Am ales pentru tine 3 website-uri potrivite pentru link insertion. Uite opțiunile cele mai bune acum:' . "\n\n- " . implode("\n- ", $preview_lines) . "\n\n" . 'Spune-mi pe care îl dorești și pregătesc comanda cu keyword-ul principal al site-ului tău.',
                'bg' => 'Подбрах за теб 3 подходящи сайта за link insertion. Ето най-добрите варианти в момента:' . "\n\n- " . implode("\n- ", $preview_lines) . "\n\n" . 'Кажи ми кой искаш и ще подготвя поръчката с основната ключова дума на сайта ти.',
                default => 'I picked 3 suitable websites for link insertion for you. Here are the best options right now:' . "\n\n- " . implode("\n- ", $preview_lines) . "\n\n" . 'Tell me which one you want and I will prepare the order with your site\'s main keyword.',
            },
            'suggested_actions' => $suggested_actions,
            'state_meta' => array(
                'offpage_purchase_flow' => 'link_insertion',
                'offpage_purchase_stage' => 'awaiting_selection',
                'offpage_purchase_selection_mode' => 'ai_shortlist',
                'offpage_purchase_candidates' => $shortlist,
                'offpage_purchase_keyword' => $seed_keyword,
            ),
        );
    }

    private function aysa_adjustments_place_link_insertion_order($project_id, $setup_user_id, array $payload, $language) {
        $portal_id = sanitize_text_field((string) ($payload['portal_id'] ?? ''));
        $portal_domain = sanitize_text_field((string) ($payload['portal_domain'] ?? ''));
        $keyword = sanitize_text_field((string) ($payload['keyword'] ?? ''));
        $cost_cents = max(0, intval($payload['estimated_cost_cents'] ?? 0));

        if ($portal_id === '' || $portal_domain === '' || $keyword === '' || $cost_cents <= 0) {
            return array(
                'module' => 'offpage_link_building',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I am missing a few details for this link insertion order, so I stopped before placing it.',
                    'Îmi lipsesc câteva detalii pentru această comandă de link insertion, așa că m-am oprit înainte să o plasez.',
                    'Липсват ми няколко детайла за тази link insertion поръчка, затова спрях преди да я пусна.'
                ),
            );
        }

        $setup = $this->aysa_get_local_setup_state_for_user($setup_user_id);
        $general = isset($setup['general']) && is_array($setup['general']) ? $setup['general'] : array();
        $business_name = sanitize_text_field((string) ($general['company_name'] ?? get_bloginfo('name')));
        $target_url = $this->aysa_setup_normalize_url((string) ($general['website'] ?? home_url('/')));
        if ($target_url === '') {
            $target_url = home_url('/');
        }
        $title = sanitize_text_field('Link insertion - ' . $keyword);

        $order_result = $this->aysa_adjustments_create_adverlink_order_internal($project_id, array(
            'portal_id' => $portal_id,
            'portal_domain' => $portal_domain,
            'service_type' => 'link_insertion',
            'billing_type' => 'one_time',
            'keyword' => $keyword,
            'target_url' => $target_url,
            'title' => $title,
            'rel_type' => 'follow',
            'estimated_credits' => $cost_cents,
            'client_name' => $business_name,
            'project_ref' => 'wp-project-' . intval($project_id),
            'order_meta' => array(
                'source' => 'adjustments_agent',
                'placement_type' => 'link_insertion',
            ),
        ));

        if (is_wp_error($order_result)) {
            if ($order_result->get_error_code() === 'insufficient_credits') {
                $error_data = $order_result->get_error_data();
                return $this->aysa_build_insufficient_credits_payment_response(
                    $project_id,
                    intval(is_array($error_data) ? ($error_data['required_credits'] ?? 0) : 0),
                    intval(is_array($error_data) ? ($error_data['available_credits'] ?? 0) : 0),
                    $language,
                    'link_insertion_order',
                    'offpage_link_building',
                    array(
                        'portal_id' => $portal_id,
                        'portal_domain' => $portal_domain,
                        'target_url' => $target_url,
                        'action_ref' => 'link_insertion_order|' . $portal_id . '|' . $target_url,
                    )
                );
            }
            return array(
                'module' => 'offpage_link_building',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I could not place the link insertion order right now: ',
                    'Nu am putut plasa acum comanda de link insertion: ',
                    'Не успях да пусна link insertion поръчката сега: '
                ) . $order_result->get_error_message(),
            );
        }

        $charged = intval($order_result['charged_credits_cents'] ?? 0);
        $included_link_applied = !empty($order_result['included_link_applied']);
        $order_ref = sanitize_text_field((string) ($order_result['order_ref'] ?? ''));

        return array(
            'module' => 'offpage_link_building',
            'assistant_message' => match ($language) {
                'ro' => 'Am trimis comanda de link insertion pentru ' . $portal_domain . '. '
                    . ($included_link_applied
                        ? 'Am folosit 1 link inclus din abonament, fără cost suplimentar în credite. '
                        : 'Am consumat acum ' . $charged . ' credite. ')
                    . 'Keyword-ul folosit este "' . $keyword . '".',
                'bg' => 'Изпратих link insertion поръчката за ' . $portal_domain . '. '
                    . ($included_link_applied
                        ? 'Използвах 1 включен линк от абонамента, без допълнителен разход на кредити. '
                        : 'Сега изразходвах ' . $charged . ' кредита. ')
                    . 'Използваната ключова дума е "' . $keyword . '".',
                default => 'I placed the link insertion order for ' . $portal_domain . '. '
                    . ($included_link_applied
                        ? 'I used 1 included subscription link, so no extra credits were charged. '
                        : 'I charged ' . $charged . ' credits now. ')
                    . 'The keyword used is "' . $keyword . '".',
            } . ($order_ref !== '' ? "\n\n" . $this->aysa_adjustments_tr($language, 'Order reference', 'Referință comandă', 'Референция на поръчка') . ': ' . $order_ref : ''),
            'suggested_actions' => array(
                array(
                    'label' => $this->aysa_adjustments_tr($language, 'See link building orders', 'Vezi comenzile de link building', 'Виж поръчките за link building'),
                    'message' => '',
                    'ui_action' => 'open_artifact_offpage_link_building_orders',
                ),
            ),
            'ui_action' => 'open_artifact_offpage_link_building_orders',
            'state_meta' => array(
                'offpage_purchase_flow' => '',
                'offpage_purchase_stage' => '',
                'offpage_purchase_selection_mode' => '',
                'offpage_purchase_candidates' => array(),
                'offpage_purchase_selected_candidate' => array(),
            ),
        );
    }

    private function aysa_adjustments_recommend_article_portals($project_id, $setup_user_id, $service_type, $language) {
        $service_type = $service_type === 'professional_article' ? 'professional_article' : 'seo_article';
        $summary = $this->aysa_get_local_setup_state_for_user($setup_user_id);
        $seed_keyword = $this->aysa_adjustments_get_seed_keyword_from_setup($summary);

        $portal_result = $this->aysa_adjustments_list_adverlink_portals_internal($project_id, $service_type, 'one_time', 50, true);
        if (is_wp_error($portal_result)) {
            return array(
                'module' => 'offpage_link_building',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I could not load article placement websites right now. We can try again in a moment.',
                    'Nu am reușit să încarc acum website-urile pentru articol. Putem încerca din nou imediat.',
                    'Не успях да заредя сайтовете за статия точно сега. Можем да опитаме отново след малко.'
                ),
            );
        }

        $items = isset($portal_result['items']) && is_array($portal_result['items']) ? $portal_result['items'] : array();
        $shortlist = $this->aysa_adjustments_build_portal_shortlist($items, $summary, $service_type, 'one_time', 3);
        if (empty($shortlist)) {
            return array(
                'module' => 'offpage_link_building',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I did not find suitable article websites right now. We can refresh the marketplace and try again.',
                    'Nu am găsit acum website-uri potrivite pentru articol. Putem reîmprospăta marketplace-ul și încerca din nou.',
                    'В момента не намерих подходящи сайтове за статия. Можем да опресним marketplace-а и да опитаме отново.'
                ),
            );
        }

        $preview_lines = array();
        $suggested_actions = array();
        foreach ($shortlist as $index => $portal) {
            $domain = (string) ($portal['domain'] ?? '');
            $authority = (float) ($portal['authority'] ?? 0);
            $cost = intval($portal['estimated_cost_cents'] ?? 0);
            $preview_lines[] = ($index + 1) . '. ' . $domain
                . ' | ' . $this->aysa_adjustments_tr($language, 'authority', 'autoritate', 'authority') . ': ' . number_format($authority, 1)
                . ' | ' . $this->aysa_adjustments_tr($language, 'one-time cost', 'cost unic', 'еднократна цена') . ': ' . $cost;
            $suggested_actions[] = array(
                'label' => $domain,
                'message' => $domain,
            );
        }

        $articleLabel = $service_type === 'professional_article'
            ? $this->aysa_adjustments_tr($language, 'professional article', 'professional article', 'professional article')
            : $this->aysa_adjustments_tr($language, 'SEO article', 'SEO article', 'SEO article');

        return array(
            'module' => 'offpage_link_building',
            'assistant_message' => match ($language) {
                'ro' => 'Am ales pentru tine 3 website-uri potrivite pentru ' . $articleLabel . '. Uite opțiunile cele mai bune acum:' . "\n\n- " . implode("\n- ", $preview_lines) . "\n\n" . 'Spune-mi pe care îl dorești și pregătesc comanda articolului.',
                'bg' => 'Подбрах за теб 3 подходящи сайта за ' . $articleLabel . '. Ето най-добрите варианти в момента:' . "\n\n- " . implode("\n- ", $preview_lines) . "\n\n" . 'Кажи ми кой искаш и ще подготвя поръчката за статия.',
                default => 'I picked 3 suitable websites for ' . $articleLabel . '. Here are the best options right now:' . "\n\n- " . implode("\n- ", $preview_lines) . "\n\n" . 'Tell me which one you want and I will prepare the article order.',
            },
            'suggested_actions' => $suggested_actions,
            'state_meta' => array(
                'offpage_purchase_flow' => $service_type,
                'offpage_purchase_stage' => 'awaiting_selection',
                'offpage_purchase_selection_mode' => 'ai_shortlist',
                'offpage_purchase_candidates' => $shortlist,
                'offpage_purchase_keyword' => $seed_keyword,
            ),
        );
    }

    private function aysa_adjustments_build_article_brief_text($business_name, $keyword, $language) {
        return match ($language) {
            'ro' => 'Scrie un articol natural și util despre ' . $keyword . ', cu context real despre ' . $business_name . ' și un link integrat natural către website-ul companiei.',
            'bg' => 'Напиши естествена и полезна статия за ' . $keyword . ', с реален контекст за ' . $business_name . ' и естествено интегриран линк към сайта на компанията.',
            default => 'Write a natural, useful article about ' . $keyword . ', with real context about ' . $business_name . ' and one naturally integrated link to the company website.',
        };
    }

    private function aysa_adjustments_place_article_order($project_id, $setup_user_id, array $payload, $service_type, $language) {
        $service_type = $service_type === 'professional_article' ? 'professional_article' : 'seo_article';
        $portal_id = sanitize_text_field((string) ($payload['portal_id'] ?? ''));
        $portal_domain = sanitize_text_field((string) ($payload['portal_domain'] ?? ''));
        $keyword = sanitize_text_field((string) ($payload['keyword'] ?? ''));
        $cost_cents = max(0, intval($payload['estimated_cost_cents'] ?? 0));

        if ($portal_id === '' || $portal_domain === '' || $keyword === '' || $cost_cents <= 0) {
            return array(
                'module' => 'offpage_link_building',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I am missing a few details for this article order, so I stopped before placing it.',
                    'Îmi lipsesc câteva detalii pentru această comandă de articol, așa că m-am oprit înainte să o plasez.',
                    'Липсват ми няколко детайла за тази поръчка за статия, затова спрях преди да я пусна.'
                ),
            );
        }

        $setup = $this->aysa_get_local_setup_state_for_user($setup_user_id);
        $general = isset($setup['general']) && is_array($setup['general']) ? $setup['general'] : array();
        $business_name = sanitize_text_field((string) ($general['company_name'] ?? get_bloginfo('name')));
        $target_url = $this->aysa_setup_normalize_url((string) ($general['website'] ?? home_url('/')));
        if ($target_url === '') {
            $target_url = home_url('/');
        }
        $article_topic = sanitize_text_field($business_name . ' - ' . $keyword);
        $article_brief = $this->aysa_adjustments_build_article_brief_text($business_name, $keyword, $language);
        $article_word_count = $service_type === 'professional_article' ? 1200 : 900;
        $article_language = $language === 'bg' ? 'bg' : ($language === 'ro' ? 'ro' : 'en');
        $title = $article_topic;

        $order_result = $this->aysa_adjustments_create_adverlink_order_internal($project_id, array(
            'portal_id' => $portal_id,
            'portal_domain' => $portal_domain,
            'service_type' => $service_type,
            'billing_type' => 'one_time',
            'keyword' => $keyword,
            'target_url' => $target_url,
            'title' => $title,
            'rel_type' => 'follow',
            'article_topic' => $article_topic,
            'article_brief' => $article_brief,
            'article_word_count' => $article_word_count,
            'article_language' => $article_language,
            'article_tone' => 'professional',
            'estimated_credits' => $cost_cents,
            'client_name' => $business_name,
            'project_ref' => 'wp-project-' . intval($project_id),
            'order_meta' => array(
                'source' => 'adjustments_agent',
                'placement_type' => $service_type,
            ),
        ));

        if (is_wp_error($order_result)) {
            if ($order_result->get_error_code() === 'insufficient_credits') {
                $error_data = $order_result->get_error_data();
                return $this->aysa_build_insufficient_credits_payment_response(
                    $project_id,
                    intval(is_array($error_data) ? ($error_data['required_credits'] ?? 0) : 0),
                    intval(is_array($error_data) ? ($error_data['available_credits'] ?? 0) : 0),
                    $language,
                    $service_type === 'professional_article' ? 'professional_article_order' : 'seo_article_order',
                    'offpage_link_building',
                    array(
                        'portal_id' => $portal_id,
                        'portal_domain' => $portal_domain,
                        'target_url' => $target_url,
                        'action_ref' => ($service_type === 'professional_article' ? 'professional_article_order|' : 'seo_article_order|') . $portal_id . '|' . $target_url,
                    )
                );
            }
            return array(
                'module' => 'offpage_link_building',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I could not place the article order right now: ',
                    'Nu am putut plasa acum comanda de articol: ',
                    'Не успях да пусна поръчката за статия сега: '
                ) . $order_result->get_error_message(),
            );
        }

        $charged = intval($order_result['charged_credits_cents'] ?? 0);
        $included_link_applied = !empty($order_result['included_link_applied']);
        $order_ref = sanitize_text_field((string) ($order_result['order_ref'] ?? ''));
        $serviceLabel = $service_type === 'professional_article'
            ? $this->aysa_adjustments_tr($language, 'professional article', 'professional article', 'professional article')
            : $this->aysa_adjustments_tr($language, 'SEO article', 'SEO article', 'SEO article');
        $publisher_draft = null;
        $publisher_draft_note = '';
        if ($service_type === 'seo_article' && is_array($order_result)) {
            $draft_result = $this->aysa_create_publisher_article_draft_for_order($project_id, $order_result);
            if (is_wp_error($draft_result)) {
                $publisher_draft_note = $draft_result->get_error_message();
            } elseif (isset($draft_result['draft']) && is_array($draft_result['draft'])) {
                $publisher_draft = $draft_result['draft'];
            }
        }

        return array(
            'module' => 'offpage_link_building',
            'assistant_message' => match ($language) {
                'ro' => 'Am trimis comanda de ' . $serviceLabel . ' pentru ' . $portal_domain . '. '
                    . ($included_link_applied
                        ? 'Am folosit 1 link inclus din abonament, fără cost suplimentar în credite. '
                        : 'Am consumat acum ' . $charged . ' credite. ')
                    . 'Tema articolului este "' . $article_topic . '".',
                'bg' => 'Изпратих поръчката за ' . $serviceLabel . ' за ' . $portal_domain . '. '
                    . ($included_link_applied
                        ? 'Използвах 1 включен линк от абонамента, без допълнителен разход на кредити. '
                        : 'Сега изразходвах ' . $charged . ' кредита. ')
                    . 'Темата на статията е "' . $article_topic . '".',
                default => 'I placed the ' . $serviceLabel . ' order for ' . $portal_domain . '. '
                    . ($included_link_applied
                        ? 'I used 1 included subscription link, so no extra credits were charged. '
                        : 'I charged ' . $charged . ' credits now. ')
                    . 'The article topic is "' . $article_topic . '".',
            } . ($order_ref !== '' ? "\n\n" . $this->aysa_adjustments_tr($language, 'Order reference', 'Referință comandă', 'Референция на поръчка') . ': ' . $order_ref : '')
              . ($publisher_draft && !empty($publisher_draft['id'])
                    ? "\n\n" . $this->aysa_adjustments_tr($language, 'Draft article created', 'Draft de articol creat', 'Създадена чернова на статия') . ': #' . intval($publisher_draft['id'])
                    : ($publisher_draft_note !== '' ? "\n\n" . $this->aysa_adjustments_tr($language, 'Draft note', 'Notă draft', 'Бележка за чернова') . ': ' . $publisher_draft_note : '')),
            'suggested_actions' => array(
                array(
                    'label' => $this->aysa_adjustments_tr($language, 'See link building orders', 'Vezi comenzile de link building', 'Виж поръчките за link building'),
                    'message' => '',
                    'ui_action' => 'open_artifact_offpage_link_building_orders',
                ),
                array(
                    'label' => $this->aysa_adjustments_tr($language, 'See draft articles', 'Vezi drafturile de articole', 'Виж черновите на статиите'),
                    'message' => '',
                    'ui_action' => 'open_artifact_offpage_publisher_drafts',
                ),
            ),
            'ui_action' => $publisher_draft ? 'open_artifact_offpage_publisher_drafts' : 'open_artifact_offpage_link_building_orders',
            'state_meta' => array(
                'offpage_purchase_flow' => '',
                'offpage_purchase_stage' => '',
                'offpage_purchase_selection_mode' => '',
                'offpage_purchase_candidates' => array(),
                'offpage_purchase_selected_candidate' => array(),
            ),
        );
    }

    private function aysa_adjustments_list_adverlink_portals_internal($project_id, $service_type, $billing_type, $per_page = 20, $force_refresh = false) {
        $creds = $this->aysa_get_adverlink_credentials();
        $api_key = $creds['api_key'];
        $advertiser_org_id = $creds['org_id'];

        $credentials_check = $this->aysa_validate_adverlink_marketplace_credentials($creds);
        if (is_wp_error($credentials_check)) {
            return $credentials_check;
        }

        $decoded = $this->aysa_broker_request('POST', '/adverlink/v1/public/portals', array(
            'project_id' => intval($project_id),
            'api_key' => $api_key,
            'advertiser_org_id' => $advertiser_org_id,
            'service_type' => sanitize_text_field((string) $service_type),
            'billing_type' => sanitize_text_field((string) $billing_type),
            'per_page' => max(1, min(100, intval($per_page))),
            'force_refresh' => !empty($force_refresh),
        ), 45);

        if (is_wp_error($decoded)) {
            return $decoded;
        }

        $items = array();
        if (is_array($decoded)) {
            if (isset($decoded['data']) && is_array($decoded['data'])) {
                $items = $decoded['data'];
            } elseif (isset($decoded['items']) && is_array($decoded['items'])) {
                $items = $decoded['items'];
            } elseif (isset($decoded['portals']) && is_array($decoded['portals'])) {
                $items = $decoded['portals'];
            } elseif (array_keys($decoded) === range(0, count($decoded) - 1)) {
                $items = $decoded;
            }
        }

        return array(
            'items' => $items,
            'meta' => isset($decoded['meta']) && is_array($decoded['meta'])
                ? $decoded['meta']
                : (isset($decoded['raw']['meta']) && is_array($decoded['raw']['meta']) ? $decoded['raw']['meta'] : array()),
            'raw' => $decoded,
        );
    }

    private function aysa_adjustments_create_adverlink_order_internal($project_id, array $args) {
        $project_id = intval($project_id);
        $creds = $this->aysa_get_adverlink_credentials();
        $api_key = $creds['api_key'];
        $advertiser_org_id = $creds['org_id'];
        if ($project_id <= 0) {
            return new WP_Error('invalid_project', 'Invalid project.');
        }

        $credentials_check = $this->aysa_validate_adverlink_marketplace_credentials($creds);
        if (is_wp_error($credentials_check)) {
            return $credentials_check;
        }

        $portal_id = sanitize_text_field((string) ($args['portal_id'] ?? ''));
        $service_type = sanitize_text_field((string) ($args['service_type'] ?? 'link_insertion'));
        $billing_type = sanitize_text_field((string) ($args['billing_type'] ?? 'one_time'));
        $keyword = sanitize_text_field((string) ($args['keyword'] ?? ''));
        $target_url = esc_url_raw(trim((string) ($args['target_url'] ?? '')));
        $title = sanitize_text_field((string) ($args['title'] ?? ''));
        $rel_type = sanitize_text_field((string) ($args['rel_type'] ?? 'follow'));
        $client_name = sanitize_text_field((string) ($args['client_name'] ?? get_bloginfo('name')));
        $project_ref = sanitize_text_field((string) ($args['project_ref'] ?? ('wp-project-' . $project_id)));
        $ad_display_url = esc_url_raw(trim((string) ($args['ad_display_url'] ?? '')));
        $ad_description = sanitize_textarea_field((string) ($args['ad_description'] ?? ''));
        $duration_months = max(1, min(12, intval($args['duration_months'] ?? 1)));
        $estimated_credits = max(0, intval($args['estimated_credits'] ?? 0));
        $portal_domain = sanitize_text_field((string) ($args['portal_domain'] ?? ''));
        $order_meta = isset($args['order_meta']) && is_array($args['order_meta']) ? $args['order_meta'] : array();
        $article_topic = sanitize_text_field((string) ($args['article_topic'] ?? ''));
        $article_brief = sanitize_textarea_field((string) ($args['article_brief'] ?? ''));
        $article_word_count = max(0, intval($args['article_word_count'] ?? 0));
        $article_language = sanitize_text_field((string) ($args['article_language'] ?? ''));
        $article_tone = sanitize_text_field((string) ($args['article_tone'] ?? ''));
        $article_content_html = wp_kses_post((string) ($args['article_content_html'] ?? ''));
        $target_article_url = esc_url_raw(trim((string) ($args['target_article_url'] ?? '')));
        $article_keywords = isset($args['article_keywords']) && is_array($args['article_keywords']) ? array_values(array_filter(array_map('sanitize_text_field', $args['article_keywords']))) : array();
        $defer_placement_billing = !empty($args['defer_placement_billing_until_public_readback'])
            && in_array($service_type, array('seo_article', 'professional_article'), true);
        $order_label = sanitize_text_field((string) ($args['order_label'] ?? ''));
        if ($order_label === '') {
            $order_label = $service_type === 'seo_ads_text'
                ? 'SEO Text Ads'
                : ($service_type === 'seo_article'
                    ? 'SEO article'
                    : ($service_type === 'professional_article' ? 'professional article' : 'link insertion'));
        }

        if ($portal_id === '' || $keyword === '' || $target_url === '' || $estimated_credits <= 0) {
            return new WP_Error('missing_fields', 'Missing required ' . $order_label . ' order fields.');
        }

        $included_link_applied = false;
        $wallet_manager = $this->aysa_get_wallet_manager_instance();
        if ($wallet_manager && $this->aysa_is_included_link_eligible_service($service_type) && method_exists($wallet_manager, 'has_included_link_available')) {
            $included_link_applied = (bool) $wallet_manager->has_included_link_available($project_id);
        }
        if ($defer_placement_billing) {
            $included_link_applied = false;
        }

        $required_credits = $defer_placement_billing ? 0 : ($included_link_applied ? 0 : $estimated_credits);
        $actual_credits = $this->aysa_get_project_credit_balance($project_id, true);
        $reserved_credits = intval(get_option($this->aysa_get_reserved_credits_key($project_id), 0));
        $available_credits = max(0, $actual_credits - $reserved_credits);
        if ($available_credits < $required_credits) {
            return new WP_Error(
                'insufficient_credits',
                'Insufficient credits for this ' . $order_label . ' order.',
                array(
                    'required_credits' => $required_credits,
                    'available_credits' => $available_credits,
                    'missing_credits' => max(0, $required_credits - $available_credits),
                    'order_label' => $order_label,
                )
            );
        }

        $payload = array(
            'portal_id' => $portal_id,
            'service_type' => $service_type,
            'billing_type' => $billing_type,
            'keyword' => $keyword,
            'target_url' => $target_url,
            'title' => $title,
            'rel_type' => $rel_type,
            'client_ref' => 'AYSA-' . $project_id . '-' . gmdate('YmdHis') . '-' . wp_generate_password(6, false, false),
            'client_name' => $client_name,
            'project_ref' => $project_ref,
            'ad_display_url' => $ad_display_url,
            'ad_description' => $ad_description,
            'duration_months' => $duration_months,
        );
        if ($service_type === 'seo_article' || $service_type === 'professional_article') {
            if ($article_topic !== '') {
                $payload['article_topic'] = $article_topic;
            }
            if ($article_brief !== '') {
                $payload['article_brief'] = $article_brief;
            }
            if ($article_word_count > 0) {
                $payload['article_word_count'] = $article_word_count;
            }
            if ($article_language !== '') {
                $payload['article_language'] = $article_language;
            }
            if ($article_tone !== '') {
                $payload['article_tone'] = $article_tone;
            }
            if (!empty($article_keywords)) {
                $payload['article_keywords'] = array_values(array_unique($article_keywords));
            }
            if ($article_content_html !== '') {
                $payload['article_content_html'] = $article_content_html;
            }
            // Adverlink SEO articles should not force sponsored rel on article links.
            $payload['rel_type'] = $rel_type === 'nofollow' ? 'nofollow' : 'follow';
        } elseif ($service_type === 'seo_ads_text' && $target_article_url !== '') {
            $payload['target_article_url'] = $target_article_url;
        }

        $idempotency_key = 'aysa-' . $project_id . '-' . wp_generate_password(16, false, false);
        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/public/orders/create', array(
            'project_id' => $project_id,
            'api_key' => $api_key,
            'advertiser_org_id' => $advertiser_org_id,
            'idempotency_key' => $idempotency_key,
            'payload' => $payload,
        ), 180);
        if (is_wp_error($broker_result)) {
            return $broker_result;
        }

        $decoded = is_array($broker_result['provider_response'] ?? null)
            ? $broker_result['provider_response']
            : (is_array($broker_result['data'] ?? null) ? $broker_result['data'] : $broker_result);
        $order_ref = is_array($decoded) ? (string) $this->aysa_extract_external_order_ref($decoded) : '';
        $order_status = is_array($decoded) ? (string) $this->aysa_extract_external_order_status($decoded) : '';
        if ($order_ref === '') {
            $order_ref = 'local-' . time();
        }
        if ($order_status === '') {
            $order_status = 'created';
        }

        $external_cost_cents = $this->aysa_extract_external_order_cost_cents($decoded);
        if ($external_cost_cents <= 0) {
            $external_cost_cents = $estimated_credits;
        }
        $charged_credits_cents = $defer_placement_billing ? 0 : ($included_link_applied ? 0 : $external_cost_cents);
        $included_link_consumption = false;
        if ($included_link_applied && $wallet_manager && method_exists($wallet_manager, 'consume_included_link')) {
            $included_link_consumption = $wallet_manager->consume_included_link(
                $project_id,
                'offpage_create_order_' . $order_ref,
                array(
                    'service_type' => $service_type,
                    'portal_id' => $portal_id,
                    'portal_domain' => $portal_domain,
                    'order_ref' => $order_ref,
                    'provider_cost_cents' => $external_cost_cents,
                )
            );
            if ($included_link_consumption === false) {
                $included_link_applied = false;
                $charged_credits_cents = $external_cost_cents;
            }
        }

        $orders = $this->aysa_get_adverlink_orders($project_id);
        $orders[] = array(
            'order_ref' => $order_ref,
            'status' => $order_status,
            'project_id' => $project_id,
            'portal_id' => $portal_id,
            'portal_domain' => $portal_domain,
            'service_type' => $service_type,
            'billing_type' => $billing_type,
            'keyword' => $keyword,
            'target_url' => $target_url,
            'title' => $title,
            'rel_type' => $rel_type,
            'client_name' => $client_name,
            'project_ref' => $project_ref,
            'ad_display_url' => $ad_display_url,
            'ad_description' => $ad_description,
            'duration_months' => $duration_months,
            'estimated_credits' => 0,
            'credits_released' => true,
            'charged_credits_cents' => $charged_credits_cents,
            'provider_cost_cents' => $external_cost_cents,
            'included_link_applied' => $included_link_applied,
            'placement_billing_deferred' => $defer_placement_billing,
            'created_at' => current_time('mysql'),
            'updated_at' => current_time('mysql'),
            'order_meta' => $order_meta,
            'verification' => $this->aysa_extract_external_verification($decoded),
            'subscription' => $this->aysa_extract_external_subscription($decoded),
            'raw_response' => $decoded,
        );
        if (count($orders) > 200) {
            $orders = array_slice($orders, -200);
        }
        $this->aysa_save_adverlink_orders($project_id, $orders);

        if ($charged_credits_cents > 0) {
            $this->aysa_wallet_track_server_usage(
                $project_id,
                'wp_ajax/aysa_adverlink_create_order',
                $charged_credits_cents,
                'offpage_create_order_' . $order_ref,
                array(
                    'module' => 'offpage',
                    'action_key' => 'aysa_adverlink_create_order',
                    'service_type' => $service_type,
                    'portal_id' => $portal_id,
                    'order_ref' => $order_ref,
                    'provider_cost_cents' => $external_cost_cents,
                    'included_link_applied' => $included_link_applied,
                )
            );
        }

        $actual_credits_after = $this->aysa_get_project_credit_balance($project_id, true);
        $reserved_credits_after = intval(get_option($this->aysa_get_reserved_credits_key($project_id), 0));

        return array(
            'order_ref' => $order_ref,
            'status' => $order_status,
            'charged_credits_cents' => $charged_credits_cents,
            'provider_cost_cents' => $external_cost_cents,
            'placement_billing_deferred' => $defer_placement_billing,
            'included_link_applied' => $included_link_applied,
            'included_link_snapshot' => is_array($included_link_consumption) ? $included_link_consumption : array(),
            'actual_credits' => $actual_credits_after,
            'reserved_credits' => $reserved_credits_after,
            'available_credits' => max(0, $actual_credits_after - $reserved_credits_after),
            'raw' => $decoded,
        );
    }

    private function aysa_adjustments_build_seo_text_ads_shortlist(array $items, array $setup_state) {
        $summary_general = isset($setup_state['general']) && is_array($setup_state['general']) ? $setup_state['general'] : array();
        $summary_competition = isset($setup_state['competition']) && is_array($setup_state['competition']) ? $setup_state['competition'] : array();
        $industry = strtolower(trim((string) ($summary_general['industry'] ?? '')));
        $business = strtolower(trim((string) ($summary_general['company_name'] ?? '')));
        $site_host = strtolower((string) wp_parse_url((string) ($summary_general['website'] ?? home_url('/')), PHP_URL_HOST));
        $seed_keywords = array();
        for ($i = 1; $i <= 5; $i++) {
            $keyword = strtolower(trim((string) ($summary_competition['keyword' . $i] ?? '')));
            if ($keyword !== '') {
                $seed_keywords[] = $keyword;
            }
        }

        $prepared = array();
        foreach ($items as $row) {
            if (!is_array($row)) {
                continue;
            }
            $domain = $this->aysa_adjustments_extract_portal_domain($row);
            if ($domain === '' || ($site_host !== '' && $domain === $site_host)) {
                continue;
            }
            $authority = $this->aysa_adjustments_extract_portal_authority($row);
            if ($authority < 10) {
                continue;
            }
            $monthly_cost = $this->aysa_adjustments_extract_portal_monthly_cost_cents($row);
            if ($monthly_cost <= 0) {
                continue;
            }

            $score = $authority;
            $haystack = strtolower(wp_json_encode($row));
            if ($industry !== '' && str_contains($haystack, $industry)) {
                $score += 4;
            }
            foreach ($seed_keywords as $seed) {
                if ($seed !== '' && str_contains($haystack, $seed)) {
                    $score += 2;
                }
            }
            if ($business !== '' && str_contains($haystack, $business)) {
                $score -= 3;
            }

            $prepared[] = array(
                'portal_id' => sanitize_text_field((string) ($row['portal_id'] ?? $row['id'] ?? '')),
                'domain' => $domain,
                'authority' => $authority,
                'monthly_cost_cents' => $monthly_cost,
                'currency' => sanitize_text_field((string) ($row['currency'] ?? 'credits')),
                'score' => $score,
            );
        }

        usort($prepared, static function(array $a, array $b): int {
            $scoreCompare = (float) ($b['score'] ?? 0) <=> (float) ($a['score'] ?? 0);
            if ($scoreCompare !== 0) {
                return $scoreCompare;
            }
            return intval($a['monthly_cost_cents'] ?? 0) <=> intval($b['monthly_cost_cents'] ?? 0);
        });

        $deduped = array();
        $seen = array();
        foreach ($prepared as $portal) {
            $key = strtolower((string) ($portal['domain'] ?? ''));
            if ($key === '' || isset($seen[$key])) {
                continue;
            }
            $seen[$key] = true;
            $deduped[] = $portal;
            if (count($deduped) >= 3) {
                break;
            }
        }

        return $deduped;
    }

    private function aysa_adjustments_extract_portal_domain(array $row) {
        foreach (array('domain', 'portal_domain', 'host', 'hostname', 'site', 'site_domain') as $key) {
            $value = trim((string) ($row[$key] ?? ''));
            if ($value !== '') {
                $value = preg_replace('#^https?://#i', '', $value);
                return strtolower(trim((string) $value, "/ \t\n\r\0\x0B"));
            }
        }
        return '';
    }

    private function aysa_adjustments_extract_portal_authority(array $row) {
        foreach (array('authority', 'domain_authority', 'domainAuthority', 'da', 'dr', 'trust_flow', 'trustFlow') as $key) {
            if (isset($row[$key]) && $row[$key] !== '') {
                return (float) $row[$key];
            }
        }
        $metrics = isset($row['metrics']) && is_array($row['metrics']) ? $row['metrics'] : array();
        foreach (array('domain_authority', 'domainAuthority', 'da', 'dr', 'trust_flow', 'trustFlow', 'ur_medium', 'qualitative_evaluation') as $key) {
            if (isset($metrics[$key]) && $metrics[$key] !== '') {
                return (float) $metrics[$key];
            }
        }
        return 0.0;
    }

    private function aysa_adjustments_extract_portal_monthly_cost_cents(array $row) {
        foreach (array('price_cents', 'monthly_price_cents', 'billing_price_cents', 'estimated_credits', 'credits_cents', 'price') as $key) {
            if (!isset($row[$key]) || $row[$key] === '') {
                continue;
            }
            $value = $row[$key];
            if (is_numeric($value)) {
                $int = (int) round((float) $value);
                if ($int > 0) {
                    return $int;
                }
            }
        }
        $services = isset($row['services']) && is_array($row['services']) ? $row['services'] : array();
        foreach ($services as $service) {
            if (!is_array($service)) {
                continue;
            }
            $serviceType = sanitize_text_field((string) ($service['service_type'] ?? ''));
            $billingType = sanitize_text_field((string) ($service['billing_type'] ?? ''));
            if ($serviceType !== 'seo_ads_text' || $billingType !== 'monthly') {
                continue;
            }
            foreach (array('sale_price_eur', 'purchase_price_eur', 'price_eur') as $priceKey) {
                if (!isset($service[$priceKey]) || $service[$priceKey] === '') {
                    continue;
                }
                $value = $service[$priceKey];
                if (is_numeric($value)) {
                    $credits = (int) round(((float) $value) * 100);
                    if ($credits > 0) {
                        return $credits;
                    }
                }
            }
        }
        return 0;
    }

    private function aysa_adjustments_build_seo_text_ads_description($business_name, $keyword) {
        $base = trim($business_name . ' - ' . $keyword);
        $description = 'Descopera ' . $keyword . ' la ' . $business_name . '. Comanda rapid online.';
        if (function_exists('mb_strlen') && mb_strlen($description, 'UTF-8') > 90) {
            $description = mb_substr($description, 0, 87, 'UTF-8') . '...';
        } elseif (!function_exists('mb_strlen') && strlen($description) > 90) {
            $description = substr($description, 0, 87) . '...';
        }
        return sanitize_text_field($description !== '' ? $description : $base);
    }

    private function aysa_adjustments_create_content_item($project_id, $setup_user_id, $payload, $language) {
        $project_id = intval($project_id);
        $setup_user_id = intval($setup_user_id);
        $payload = is_array($payload) ? $payload : array();

        $content_type = sanitize_key((string) ($payload['content_type'] ?? 'post'));
        if (!in_array($content_type, array('post', 'page', 'product', 'category'), true)) {
            $content_type = 'post';
        }
        $title = sanitize_text_field((string) ($payload['title'] ?? ''));
        $publish_mode = sanitize_key((string) ($payload['publish_mode'] ?? 'draft'));
        if (!in_array($publish_mode, array('draft', 'publish'), true)) {
            $publish_mode = 'draft';
        }

        if ($title === '') {
            return array(
                'module' => 'content_strategy',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I need a clearer title before I can create this content item.',
                    'Am nevoie de un titlu mai clar înainte să pot crea acest item de conținut.',
                    'Имам нужда от по-ясно заглавие, преди да мога да създам този content елемент.'
                ),
            );
        }

        if (!current_user_can('edit_posts')) {
            return array(
                'module' => 'content_strategy',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I do not have permission to create content items in WordPress.',
                    'Nu am permisiunea să creez itemi de conținut în WordPress.',
                    'Нямам право да създавам content елементи в WordPress.'
                ),
            );
        }

        $main_keyword = $title;
        $edit_url = '';
        $view_url = '';
        $entity_id = 0;
        $queued_generation = false;
        $pending_queue_key = '';
        $reused_existing_entity = false;

        $existing_entity = $this->aysa_adjustments_find_existing_content_entity($content_type, $title);
        if (is_array($existing_entity) && !empty($existing_entity['entity_id'])) {
            $entity_id = intval($existing_entity['entity_id']);
            $edit_url = (string) ($existing_entity['edit_url'] ?? '');
            $view_url = (string) ($existing_entity['view_url'] ?? '');
            $reused_existing_entity = true;
        }

        if ($entity_id <= 0 && $content_type === 'category') {
            $term = wp_insert_term($title, 'category', array(
                'slug' => sanitize_title($title),
            ));
            if (is_wp_error($term)) {
                return array(
                    'module' => 'content_strategy',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I could not create that category right now: ',
                        'Nu am putut crea categoria acum: ',
                        'Не успях да създам категорията сега: '
                    ) . $term->get_error_message(),
                );
            }
            $entity_id = intval($term['term_id'] ?? 0);
            $edit_url = admin_url('term.php?taxonomy=category&tag_ID=' . $entity_id . '&post_type=post');
            $view_url = get_term_link($entity_id, 'category');
            if (is_wp_error($view_url)) {
                $view_url = '';
            }
        } elseif ($entity_id <= 0) {
            if ($content_type === 'post' && class_exists('Aysa_Content_Strategy_Create_Post')) {
                $result = Aysa_Content_Strategy_Create_Post::create_post_programmatically(array(
                    'title' => $title,
                    'keywords' => array($main_keyword),
                    'intent' => 'I',
                    'associated_pages' => array(),
                    'associated_page' => '',
                    'strategy_id' => 0,
                    'source' => 'adjustments-agent',
                ));
                if (is_wp_error($result)) {
                    return array(
                        'module' => 'content_strategy',
                        'assistant_message' => $this->aysa_adjustments_tr(
                            $language,
                            'I could not create that article right now: ',
                            'Nu am putut crea acel articol acum: ',
                            'Не успях да създам тази статия сега: '
                        ) . $result->get_error_message(),
                    );
                }

                $entity_id = intval($result['post_id'] ?? 0);
                $edit_url = (string) ($result['edit_url'] ?? '');
                $view_url = (string) ($result['view_url'] ?? '');
                $queued_generation = !empty($result['generation_queued']);
            } else {
            $post_status = $publish_mode === 'publish' ? 'publish' : 'draft';
            $post_data = array(
                'post_title' => $title,
                'post_content' => '',
                'post_status' => $post_status,
                'post_type' => $content_type,
                'post_author' => get_current_user_id(),
            );
            $entity_id = wp_insert_post($post_data, true);
            if (is_wp_error($entity_id)) {
                return array(
                    'module' => 'content_strategy',
                    'assistant_message' => $this->aysa_adjustments_tr(
                        $language,
                        'I could not create that content item right now: ',
                        'Nu am putut crea acel item de conținut acum: ',
                        'Не успях да създам този content елемент сега: '
                    ) . $entity_id->get_error_message(),
                );
            }
            $entity_id = intval($entity_id);
            $edit_url = (string) get_edit_post_link($entity_id, 'raw');
            $view_url = (string) get_permalink($entity_id);

            if ($content_type === 'post') {
                $keyword_data = array(
                    'article_topic'  => $title,
                    'keyword'        => $main_keyword,
                    'second_keyword' => '',
                    'intent'         => 'I',
                    'source'         => 'adjustments-agent',
                );
                update_post_meta($entity_id, 'aysa_keyword_data', $keyword_data);
                update_post_meta($entity_id, 'aysa_main_keyword', $main_keyword);
                update_post_meta($entity_id, 'aysa_secondary_keywords', '');
                update_post_meta($entity_id, 'aysa_search_intent', 'I');
                update_post_meta($entity_id, 'aysa_generation_source', 'adjustments-agent');

                $generation_payload = array(
                    'project_id' => $project_id,
                    'post_id' => $entity_id,
                    'title' => $title,
                    'main_keyword' => $main_keyword,
                    'secondary_keywords' => array(),
                    'associated_pages' => array(),
                );
                update_post_meta($entity_id, '_aysa_existing_post_generation_status', 'queued');
                update_post_meta($entity_id, '_aysa_existing_post_generation_queued_at', current_time('mysql'));
                delete_post_meta($entity_id, '_aysa_existing_post_generation_error');
                wp_schedule_single_event(time() + 2, 'aysa_generate_existing_strategy_post', array($generation_payload));
                $queued_generation = true;
            } elseif (in_array($content_type, array('page', 'product'), true)) {
                $queued_generation = true;
            }
            }
        }

        if ($entity_id > 0 && $content_type !== 'category') {
            update_post_meta($entity_id, '_aysa_adjustments_project_id', $project_id);
            update_post_meta($entity_id, '_aysa_adjustments_notification_sent', '0');
        }

        if ($entity_id > 0 && $content_type !== 'post' && in_array($content_type, array('page', 'product', 'category'), true)) {
            $pending_queue_key = $this->aysa_adjustments_queue_created_entity_generation($project_id, array(
                'entity_id' => $entity_id,
                'content_type' => $content_type,
                'title' => $title,
                'keyword' => $main_keyword,
                'edit_url' => $edit_url,
                'view_url' => $view_url,
                'publish_mode' => $publish_mode,
            ));
            if ($pending_queue_key !== '') {
                $queued_generation = true;
            }
        }

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_content_create_item',
            'adjustments_content_create_item',
            array(
                'module' => 'content_strategy',
                'content_type' => $content_type,
                'entity_id' => $entity_id,
                'publish_mode' => $publish_mode,
            ),
            30,
            '/v1/conversation/adjustments/content-create'
        );

        $type_label = match ($content_type) {
            'page' => $this->aysa_adjustments_tr($language, 'page', 'pagină', 'страница'),
            'product' => $this->aysa_adjustments_tr($language, 'product', 'produs', 'продукт'),
            'category' => $this->aysa_adjustments_tr($language, 'category', 'categorie', 'категория'),
            default => $this->aysa_adjustments_tr($language, 'article', 'articol', 'статия'),
        };

        $status_label = $publish_mode === 'publish'
            ? $this->aysa_adjustments_tr($language, 'published', 'publicat', 'публикуван')
            : $this->aysa_adjustments_tr($language, 'created as draft', 'creat ca draft', 'създаден като чернова');

        if ($queued_generation) {
            $message = match ($content_type) {
                'page' => match ($language) {
                    'ro' => 'Lucrez acum la pagina "' . $title . '". Îi pregătesc conținutul și optimizarea SEO și te voi anunța aici imediat ce este gata.',
                    'bg' => 'Работя по страницата "' . $title . '". Подготвям съдържанието и SEO оптимизацията и ще те уведомя тук веднага щом е готова.',
                    default => 'I am working on the page "' . $title . '" now. I am preparing the content and SEO optimization and I will notify you here as soon as it is ready.',
                },
                'product' => match ($language) {
                    'ro' => 'Lucrez acum la produsul "' . $title . '". Îi pregătesc conținutul și optimizarea SEO și te voi anunța aici imediat ce este gata.',
                    'bg' => 'Работя по продукта "' . $title . '". Подготвям съдържанието и SEO оптимизацията и ще те уведомя тук веднага щом е готов.',
                    default => 'I am working on the product "' . $title . '" now. I am preparing the content and SEO optimization and I will notify you here as soon as it is ready.',
                },
                'category' => match ($language) {
                    'ro' => 'Lucrez acum la categoria "' . $title . '". Îi pregătesc optimizarea SEO și te voi anunța aici imediat ce este gata.',
                    'bg' => 'Работя по категорията "' . $title . '". Подготвям SEO оптимизацията и ще те уведомя тук веднага щом е готова.',
                    default => 'I am working on the category "' . $title . '" now. I am preparing the SEO optimization and I will notify you here as soon as it is ready.',
                },
                default => match ($language) {
                    'ro' => 'Lucrez acum la articolul "' . $title . '". Încă îl generez și te voi anunța aici imediat ce este gata.',
                    'bg' => 'Работя по статията "' . $title . '". Все още я генерирам и ще те уведомя тук веднага щом е готова.',
                    default => 'I am working on the article "' . $title . '" now. I am still generating it and I will notify you here as soon as it is ready.',
                },
            };
        } elseif ($reused_existing_entity) {
            $message = match ($language) {
                'ro' => 'Am găsit deja ' . $type_label . 'ul "' . $title . '" și nu l-am creat din nou. Dacă vrei, îl pot optimiza sau îți pot arăta linkul final.',
                'bg' => 'Вече намерих ' . $type_label . ' "' . $title . '" и не го създадох отново. Ако искаш, мога да го оптимизирам или да ти покажа крайния линк.',
                default => 'I already found the ' . $type_label . ' "' . $title . '" and did not create it again. If you want, I can optimize it or show you the final link.',
            };
            if ($view_url !== '') {
                $message .= "\n" . $this->aysa_adjustments_tr($language, 'Final link:', 'Link final:', 'Финален линк:') . ' ' . $view_url;
            } elseif ($edit_url !== '') {
                $message .= "\n" . $this->aysa_adjustments_tr($language, 'Edit link:', 'Link de editare:', 'Линк за редакция:') . ' ' . $edit_url;
            }
        } else {
            $message = match ($language) {
                'ro' => 'Am ' . $status_label . ' ' . $type_label . 'ul "' . $title . '".',
                'bg' => 'Създадох ' . $status_label . ' ' . $type_label . ' "' . $title . '".',
                default => 'I ' . $status_label . ' the ' . $type_label . ' "' . $title . '".',
            };
            if ($edit_url !== '') {
                $message .= "\n" . $this->aysa_adjustments_tr($language, 'Edit link:', 'Link de editare:', 'Линк за редакция:') . ' ' . $edit_url;
            }
            if ($view_url !== '') {
                $message .= "\n" . $this->aysa_adjustments_tr($language, 'View link:', 'Link de vizualizare:', 'Линк за преглед:') . ' ' . $view_url;
            }
        }

        $response = array(
            'module' => 'content_strategy',
            'assistant_message' => $message,
            'suggested_actions' => array(
                array(
                    'label' => $this->aysa_adjustments_tr($language, 'See content plan', 'Vezi planul de conținut', 'Виж content плана'),
                    'message' => '',
                    'ui_action' => 'open_artifact_content_strategy',
                ),
            ),
            'state_meta' => array(
                'last_created_content_type' => $content_type,
                'last_created_entity_id' => $entity_id,
            ),
        );
        if ($queued_generation && $content_type === 'post' && $entity_id > 0) {
            $response['state_meta']['pending_generated_post_id'] = $entity_id;
            $response['state_meta']['pending_generated_post_title'] = $title;
        }
        if ($queued_generation && $entity_id > 0) {
            $response['state_meta']['pending_generated_item_id'] = $entity_id;
            $response['state_meta']['pending_generated_item_title'] = $title;
            $response['state_meta']['pending_generated_item_type'] = $content_type;
            if ($pending_queue_key !== '') {
                $response['state_meta']['pending_generated_item_key'] = $pending_queue_key;
            }
        }
        return $response;
    }

    private function aysa_adjustments_optimize_content_item($project_id, $payload, $language) {
        $project_id = intval($project_id);
        $payload = is_array($payload) ? $payload : array();

        $entity_id = intval($payload['entity_id'] ?? 0);
        $entity_type = sanitize_text_field((string) ($payload['entity_type'] ?? ''));
        $content_type = sanitize_key((string) ($payload['content_type'] ?? 'page'));
        $title = sanitize_text_field((string) ($payload['title'] ?? ''));
        $keyword = sanitize_text_field((string) ($payload['keyword'] ?? ''));
        $edit_url = esc_url_raw((string) ($payload['edit_url'] ?? ''));
        $view_url = esc_url_raw((string) ($payload['view_url'] ?? ''));

        if ($entity_id <= 0 || $entity_type === '' || $keyword === '') {
            return array(
                'module' => 'onpage_optimization',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I need a valid page, product, category, or article with an assigned keyword before I can optimize it.',
                    'Am nevoie de o pagină, un produs, o categorie sau un articol valid, cu un keyword asociat, înainte să îl pot optimiza.',
                    'Имам нужда от валидна страница, продукт, категория или статия с назначен keyword, преди да мога да я оптимизирам.'
                ),
            );
        }

        if ($this->aysa_adjustments_has_pending_seo_review($entity_id, $entity_type, $keyword)) {
            return array(
                'module' => 'onpage_optimization',
                'assistant_message' => match ($language) {
                    'ro' => 'Am deja o optimizare în lucru pentru "' . $title . '" pe keyword-ul "' . $keyword . '". O las să termine și te anunț aici imediat ce este gata.',
                    'bg' => 'Вече имам текуща оптимизация за "' . $title . '" по keyword-а "' . $keyword . '". Оставям я да приключи и ще те уведомя тук веднага щом е готова.',
                    default => 'I already have an optimization running for "' . $title . '" on the keyword "' . $keyword . '". I will let it finish and notify you here as soon as it is ready.',
                },
            );
        }

        $queue_key = $this->aysa_adjustments_queue_created_entity_generation($project_id, array(
            'entity_id' => $entity_id,
            'content_type' => $content_type,
            'title' => $title,
            'keyword' => $keyword,
            'edit_url' => $edit_url,
            'view_url' => $view_url,
            'publish_mode' => 'draft',
        ));

        return array(
            'module' => 'onpage_optimization',
            'assistant_message' => match ($language) {
                'ro' => 'Lucrez acum la optimizarea pentru "' . $title . '" pe keyword-ul "' . $keyword . '". O trimit în pipeline-ul SEO existent și te anunț aici imediat ce este gata.',
                'bg' => 'Работя сега по оптимизацията за "' . $title . '" по keyword-а "' . $keyword . '". Изпращам я към съществуващия SEO pipeline и ще те уведомя тук веднага щом е готова.',
                default => 'I am working now on the optimization for "' . $title . '" on the keyword "' . $keyword . '". I am sending it through the existing SEO pipeline and I will notify you here as soon as it is ready.',
            },
            'state_meta' => array(
                'pending_generated_item_id' => $entity_id,
                'pending_generated_item_title' => $title,
                'pending_generated_item_type' => $content_type,
                'pending_generated_item_key' => $queue_key,
            ),
        );
    }

    private function aysa_adjustments_collect_generation_notifications($project_id, $language) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array('pending' => array(), 'completed' => array(), 'failed' => array());
        }

        $query = new WP_Query(array(
            'post_type' => 'post',
            'post_status' => array('draft', 'publish', 'future', 'pending', 'private'),
            'posts_per_page' => 20,
            'orderby' => 'date',
            'order' => 'DESC',
            'fields' => 'ids',
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Reviewed WordPress query: the metadata selector is required by the feature and is constrained by the surrounding post type, status, limit, or administrative scope.
            'meta_query' => array(
                'relation' => 'AND',
                array(
                    'key' => 'aysa_generation_source',
                    'value' => 'adjustments-agent',
                ),
                array(
                    'key' => '_aysa_adjustments_project_id',
                    'value' => $project_id,
                    'compare' => '=',
                    'type' => 'NUMERIC',
                ),
                array(
                    'key' => '_aysa_adjustments_notification_sent',
                    'value' => '1',
                    'compare' => '!=',
                ),
            ),
        ));

        $pending = array();
        $completed = array();
        $failed = array();

        $keyword_refresh_status = $this->aysa_keyword_discovery_get_status($project_id);
        $keyword_refresh_state = (string) ($keyword_refresh_status['status'] ?? 'idle');
        $keyword_refresh_notified = (string) ($keyword_refresh_status['notification_sent'] ?? '0') === '1';
        $keyword_refresh_item = $this->aysa_keyword_discovery_build_pending_item($project_id);
        if ($keyword_refresh_state === 'running') {
            $pending[] = array(
                'item_key' => (string) ($keyword_refresh_item['item_key'] ?? ('keyword_refresh:' . $project_id)),
                'item_type' => 'keyword_refresh',
                'post_id' => intval($keyword_refresh_item['post_id'] ?? $project_id),
                'title' => (string) ($keyword_refresh_item['title'] ?? 'Keyword refresh'),
                'message' => match ($language) {
                    'ro' => 'Încă lucrez la refresh-ul de keyworduri. Îți arăt imediat rezultatul final când este gata.',
                    'bg' => 'Все още работя по keyword refresh-а. Ще ти покажа крайния резултат веднага щом е готов.',
                    default => 'I am still working on the keyword refresh. I will show you the final result as soon as it is ready.',
                },
            );
        } elseif ($keyword_refresh_state === 'completed' && !$keyword_refresh_notified) {
            $completed[] = array(
                'item_key' => (string) ($keyword_refresh_item['item_key'] ?? ('keyword_refresh:' . $project_id)),
                'post_id' => intval($keyword_refresh_item['post_id'] ?? $project_id),
                'title' => (string) ($keyword_refresh_item['title'] ?? 'Keyword refresh'),
                'message' => match ($language) {
                    'ro' => 'Keyword refresh este gata. Am pregătit ' . intval($keyword_refresh_status['staged'] ?? 0) . ' keyworduri, cu ' . intval($keyword_refresh_status['inserted'] ?? 0) . ' inserate și ' . intval($keyword_refresh_status['updated'] ?? 0) . ' actualizate.'
                        . ((string) ($keyword_refresh_status['metrics_notice'] ?? '') !== '' ? ' ' . (string) $keyword_refresh_status['metrics_notice'] : ''),
                    'bg' => 'Keyword refresh-ът е готов. Подготвих ' . intval($keyword_refresh_status['staged'] ?? 0) . ' keyword-а, с ' . intval($keyword_refresh_status['inserted'] ?? 0) . ' нови и ' . intval($keyword_refresh_status['updated'] ?? 0) . ' обновени.'
                        . ((string) ($keyword_refresh_status['metrics_notice'] ?? '') !== '' ? ' ' . (string) $keyword_refresh_status['metrics_notice'] : ''),
                    default => 'The keyword refresh is ready. I prepared ' . intval($keyword_refresh_status['staged'] ?? 0) . ' keywords, with ' . intval($keyword_refresh_status['inserted'] ?? 0) . ' inserted and ' . intval($keyword_refresh_status['updated'] ?? 0) . ' updated.'
                        . ((string) ($keyword_refresh_status['metrics_notice'] ?? '') !== '' ? ' ' . (string) $keyword_refresh_status['metrics_notice'] : ''),
                },
            );
            $this->notify_app_aysa_conversation_event(
                'task_completed',
                'Keyword refresh',
                admin_url('admin.php?page=aysa-research&tab=keyword-discovery'),
                array(
                    'project_id' => $project_id,
                    'resource_type' => 'project',
                    'resource_id' => (string) $project_id,
                    'job_type' => 'keyword_refresh',
                    'staged' => intval($keyword_refresh_status['staged'] ?? 0),
                    'inserted' => intval($keyword_refresh_status['inserted'] ?? 0),
                    'updated' => intval($keyword_refresh_status['updated'] ?? 0),
                )
            );
            $keyword_refresh_status['notification_sent'] = '1';
            $this->aysa_keyword_discovery_save_status($project_id, $keyword_refresh_status);
        } elseif ($keyword_refresh_state === 'failed' && !$keyword_refresh_notified) {
            $failed[] = array(
                'item_key' => (string) ($keyword_refresh_item['item_key'] ?? ('keyword_refresh:' . $project_id)),
                'post_id' => intval($keyword_refresh_item['post_id'] ?? $project_id),
                'title' => (string) ($keyword_refresh_item['title'] ?? 'Keyword refresh'),
                'message' => match ($language) {
                    'ro' => 'Keyword refresh a eșuat.' . ((string) ($keyword_refresh_status['last_error'] ?? '') !== '' ? ' ' . (string) $keyword_refresh_status['last_error'] : ''),
                    'bg' => 'Keyword refresh-ът се провали.' . ((string) ($keyword_refresh_status['last_error'] ?? '') !== '' ? ' ' . (string) $keyword_refresh_status['last_error'] : ''),
                    default => 'The keyword refresh failed.' . ((string) ($keyword_refresh_status['last_error'] ?? '') !== '' ? ' ' . (string) $keyword_refresh_status['last_error'] : ''),
                },
            );
            $this->notify_app_aysa_conversation_event(
                'task_failed',
                'Keyword refresh',
                admin_url('admin.php?page=aysa-research&tab=keyword-discovery'),
                array(
                    'project_id' => $project_id,
                    'resource_type' => 'project',
                    'resource_id' => (string) $project_id,
                    'job_type' => 'keyword_refresh',
                    'last_error' => (string) ($keyword_refresh_status['last_error'] ?? ''),
                )
            );
            $keyword_refresh_status['notification_sent'] = '1';
            $this->aysa_keyword_discovery_save_status($project_id, $keyword_refresh_status);
        }

        foreach ((array) $query->posts as $post_id) {
            $post_id = intval($post_id);
            if ($post_id <= 0) {
                continue;
            }
            $status = (string) get_post_meta($post_id, '_aysa_existing_post_generation_status', true);
            $title = (string) get_the_title($post_id);
            if ($status === 'generated') {
                $post_status = (string) get_post_status($post_id);
                $url = $post_status === 'publish'
                    ? (string) get_permalink($post_id)
                    : (string) get_edit_post_link($post_id, 'raw');
                $completed[] = array(
                    'post_id' => $post_id,
                    'title' => $title,
                    'url' => $url,
                    'message' => match ($language) {
                        'ro' => 'Articolul "' . $title . '" este gata.' . ($url !== '' ? ' Îți las aici linkul final: ' . esc_url_raw($url) : ''),
                        'bg' => 'Статията "' . $title . '" е готова.' . ($url !== '' ? ' Оставям ти финалния линк тук: ' . esc_url_raw($url) : ''),
                        default => 'The article "' . $title . '" is ready.' . ($url !== '' ? ' Here is the final link: ' . esc_url_raw($url) : ''),
                    },
                );
                update_post_meta($post_id, '_aysa_adjustments_notification_sent', '1');
                continue;
            }
            if ($status === 'failed') {
                $error = (string) get_post_meta($post_id, '_aysa_existing_post_generation_error', true);
                $failed[] = array(
                    'post_id' => $post_id,
                    'title' => $title,
                    'message' => match ($language) {
                        'ro' => 'Nu am reușit să finalizez articolul "' . $title . '"' . ($error !== '' ? ': ' . $error : '.'),
                        'bg' => 'Не успях да завърша статията "' . $title . '"' . ($error !== '' ? ': ' . $error : '.'),
                        default => 'I could not finish the article "' . $title . '"' . ($error !== '' ? ': ' . $error : '.'),
                    },
                );
                update_post_meta($post_id, '_aysa_adjustments_notification_sent', '1');
                continue;
            }

            $pending[] = array(
                'item_key' => 'post:' . $post_id,
                'item_type' => 'post',
                'post_id' => $post_id,
                'title' => $title,
                'message' => match ($language) {
                    'ro' => 'Încă lucrez la articolul "' . $title . '". Te anunț aici imediat ce este gata.',
                    'bg' => 'Все още работя по статията "' . $title . '". Ще те уведомя тук веднага щом е готова.',
                    default => 'I am still working on the article "' . $title . '". I will notify you here as soon as it is ready.',
                },
            );
        }

        $queue = $this->aysa_adjustments_get_generation_queue($project_id);
        foreach ($queue as $item_key => $item) {
            if (!is_array($item)) {
                continue;
            }
            $title = (string) ($item['title'] ?? '');
            $item_type = (string) ($item['item_type'] ?? 'item');
            $entity_id = intval($item['entity_id'] ?? 0);
            $status = (string) ($item['status'] ?? 'queued');
            if (in_array($status, array('queued', 'processing'), true)) {
                $matched_review = $this->aysa_adjustments_find_pending_review_for_entity(
                    $entity_id,
                    (string) ($item['entity_processor_type'] ?? ''),
                    (string) ($item['keyword'] ?? '')
                );
                if (!empty($matched_review['id'])) {
                    $item['status'] = 'generated';
                    $item['review_id'] = intval($matched_review['id']);
                    $item['updated_at'] = current_time('mysql');
                    $queue[$item_key] = $item;
                    $status = 'generated';
                }
            }
            if ($status === 'generated') {
                $edit_url = (string) ($item['edit_url'] ?? '');
                $view_url = (string) ($item['view_url'] ?? '');
                $link = $view_url !== '' ? $view_url : $edit_url;
                $item_type_label = match ($item_type) {
                    'page' => $this->aysa_adjustments_tr($language, 'page', 'pagina', 'страницата'),
                    'product' => $this->aysa_adjustments_tr($language, 'product', 'produsul', 'продуктът'),
                    'category' => $this->aysa_adjustments_tr($language, 'category', 'categoria', 'категорията'),
                    default => $this->aysa_adjustments_tr($language, 'item', 'elementul', 'елементът'),
                };
                $completed[] = array(
                    'item_key' => (string) $item_key,
                    'item_type' => $item_type,
                    'post_id' => $entity_id,
                    'title' => $title,
                    'message' => match ($language) {
                        'ro' => ucfirst($item_type_label) . ' "' . $title . '" este gata.' . ($link !== '' ? ' Îți las aici linkul final: ' . esc_url_raw($link) : ''),
                        'bg' => ucfirst($item_type_label) . ' "' . $title . '" е готов.' . ($link !== '' ? ' Оставям ти финалния линк тук: ' . esc_url_raw($link) : ''),
                        default => 'The ' . $item_type_label . ' "' . $title . '" is ready.' . ($link !== '' ? ' Here is the final link: ' . esc_url_raw($link) : ''),
                    },
                );
                unset($queue[$item_key]);
                continue;
            }
            if ($status === 'failed') {
                $error = trim((string) ($item['error'] ?? ''));
                $failed[] = array(
                    'item_key' => (string) $item_key,
                    'item_type' => $item_type,
                    'post_id' => $entity_id,
                    'title' => $title,
                    'message' => match ($language) {
                        'ro' => 'Nu am reușit să finalizez "' . $title . '"' . ($error !== '' ? ': ' . $error : '.'),
                        'bg' => 'Не успях да завърша "' . $title . '"' . ($error !== '' ? ': ' . $error : '.'),
                        default => 'I could not finish "' . $title . '"' . ($error !== '' ? ': ' . $error : '.'),
                    },
                );
                unset($queue[$item_key]);
                continue;
            }
            $pending[] = array(
                'item_key' => (string) $item_key,
                'item_type' => $item_type,
                'post_id' => $entity_id,
                'title' => $title,
                'message' => match ($language) {
                    'ro' => match ($item_type) {
                        'page' => 'Încă lucrez la pagina "' . $title . '". Te anunț aici imediat ce este gata.',
                        'product' => 'Încă lucrez la produsul "' . $title . '". Te anunț aici imediat ce este gata.',
                        'category' => 'Încă lucrez la categoria "' . $title . '". Te anunț aici imediat ce este gata.',
                        default => 'Încă lucrez la "' . $title . '". Te anunț aici imediat ce este gata.',
                    },
                    'bg' => match ($item_type) {
                        'page' => 'Все още работя по страницата "' . $title . '". Ще те уведомя тук веднага щом е готова.',
                        'product' => 'Все още работя по продукта "' . $title . '". Ще те уведомя тук веднага щом е готов.',
                        'category' => 'Все още работя по категорията "' . $title . '". Ще те уведомя тук веднага щом е готова.',
                        default => 'Все още работя по "' . $title . '". Ще te уведомя тук веднага щом е готово.',
                    },
                    default => match ($item_type) {
                        'page' => 'I am still working on the page "' . $title . '". I will notify you here as soon as it is ready.',
                        'product' => 'I am still working on the product "' . $title . '". I will notify you here as soon as it is ready.',
                        'category' => 'I am still working on the category "' . $title . '". I will notify you here as soon as it is ready.',
                        default => 'I am still working on "' . $title . '". I will notify you here as soon as it is ready.',
                    },
                },
            );
        }
        $this->aysa_adjustments_save_generation_queue($project_id, $queue);

        return array(
            'pending' => $pending,
            'completed' => $completed,
            'failed' => $failed,
        );
    }

    private function aysa_adjustments_find_pending_review_for_entity($entity_id, $entity_type, $keyword = '') {
        global $wpdb;

        $entity_id = intval($entity_id);
        $entity_type = sanitize_text_field((string) $entity_type);
        $keyword = sanitize_text_field((string) $keyword);
        if ($entity_id <= 0 || $entity_type === '') {
            return null;
        }

        $table = esc_sql($wpdb->prefix . 'aysa_automated_pending_reviews');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if ($table_exists !== $table) {
            return null;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $row = $wpdb->get_row(
            $wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT id, entity_id, entity_type, keyword, status, created_at
" .
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
                "                 FROM {$table}
                 WHERE entity_id = %d
                   AND entity_type = %s
                 ORDER BY id DESC
                 LIMIT 1",
                $entity_id,
                $entity_type
            ),
            ARRAY_A
        );
        if (!is_array($row) || empty($row['id'])) {
            return null;
        }

        if ($keyword !== '') {
            $stored_keyword = sanitize_text_field((string) ($row['keyword'] ?? ''));
            if ($stored_keyword !== '' && $stored_keyword !== $keyword) {
                return null;
            }
        }

        return $row;
    }

    private function aysa_adjustments_generation_queue_key($project_id) {
        return 'aysa_adjustments_generation_queue_' . intval($project_id);
    }

    private function aysa_adjustments_get_generation_queue($project_id) {
        $queue = get_option($this->aysa_adjustments_generation_queue_key($project_id), array());
        return is_array($queue) ? $queue : array();
    }

    private function aysa_adjustments_save_generation_queue($project_id, $queue) {
        update_option($this->aysa_adjustments_generation_queue_key($project_id), is_array($queue) ? $queue : array(), false);
    }

    private function aysa_adjustments_queue_created_entity_generation($project_id, $data) {
        $project_id = intval($project_id);
        $data = is_array($data) ? $data : array();
        $entity_id = intval($data['entity_id'] ?? 0);
        $content_type = sanitize_key((string) ($data['content_type'] ?? 'page'));
        $title = sanitize_text_field((string) ($data['title'] ?? ''));
        $keyword = sanitize_text_field((string) ($data['keyword'] ?? $title));
        $entity_processor_type = match ($content_type) {
            'page' => 'Page',
            'product' => 'Product',
            'category' => 'Category',
            default => 'Post',
        };
        $queue = $this->aysa_adjustments_get_generation_queue($project_id);
        foreach ($queue as $existing_key => $existing_item) {
            if (!is_array($existing_item)) {
                continue;
            }
            if (intval($existing_item['entity_id'] ?? 0) !== $entity_id) {
                continue;
            }
            if (sanitize_key((string) ($existing_item['item_type'] ?? '')) !== $content_type) {
                continue;
            }
            if (trim((string) ($existing_item['keyword'] ?? '')) !== $keyword) {
                continue;
            }
            $existing_status = sanitize_key((string) ($existing_item['status'] ?? 'queued'));
            if (in_array($existing_status, array('queued', 'processing', 'generated'), true)) {
                return (string) $existing_key;
            }
        }
        $queue_key = $content_type . ':' . $entity_id . ':' . substr(md5($title . '|' . $keyword), 0, 10);
        $queue[$queue_key] = array(
            'item_key' => $queue_key,
            'item_type' => $content_type,
            'entity_id' => $entity_id,
            'entity_processor_type' => $entity_processor_type,
            'title' => $title,
            'keyword' => $keyword,
            'edit_url' => (string) ($data['edit_url'] ?? ''),
            'view_url' => (string) ($data['view_url'] ?? ''),
            'publish_mode' => sanitize_key((string) ($data['publish_mode'] ?? 'draft')),
            'status' => 'queued',
            'created_at' => current_time('mysql'),
            'updated_at' => current_time('mysql'),
        );
        $this->aysa_adjustments_save_generation_queue($project_id, $queue);
        wp_schedule_single_event(time() + 2, 'aysa_adjustments_generate_created_entity', array(array(
            'queue_key' => $queue_key,
            'project_id' => $project_id,
        )));
        return $queue_key;
    }

    private function aysa_adjustments_process_generation_queue_now($project_id, $limit = 3) {
        $project_id = intval($project_id);
        $limit = max(1, min(10, intval($limit)));
        if ($project_id <= 0) {
            return;
        }

        $queue = $this->aysa_adjustments_get_generation_queue($project_id);
        if (empty($queue)) {
            return;
        }

        $requeue_after_seconds = 180;
        $queue_changed = false;
        foreach ($queue as $item_key => $item) {
            if (!is_array($item)) {
                continue;
            }
            $status = sanitize_key((string) ($item['status'] ?? 'queued'));
            if ($status !== 'processing') {
                continue;
            }
            $updated_at = strtotime((string) ($item['updated_at'] ?? ''));
            if ($updated_at <= 0 || (time() - $updated_at) < $requeue_after_seconds) {
                continue;
            }
            $item['status'] = 'queued';
            $item['updated_at'] = current_time('mysql');
            $item['retries'] = intval($item['retries'] ?? 0) + 1;
            $queue[$item_key] = $item;
            $queue_changed = true;
        }
        if ($queue_changed) {
            $this->aysa_adjustments_save_generation_queue($project_id, $queue);
        }

        $processed = 0;
        foreach ($queue as $item_key => $item) {
            if ($processed >= $limit) {
                break;
            }
            if (!is_array($item)) {
                continue;
            }
            $status = sanitize_key((string) ($item['status'] ?? 'queued'));
            if ($status !== 'queued') {
                continue;
            }
            $this->handle_adjustments_generate_created_entity(array(
                'queue_key' => (string) $item_key,
                'project_id' => $project_id,
            ));
            $processed++;
        }
    }

    private function aysa_adjustments_find_existing_content_entity($content_type, $title) {
        $content_type = sanitize_key((string) $content_type);
        $title = sanitize_text_field((string) $title);
        if ($content_type === '' || $title === '') {
            return null;
        }

        $slug = sanitize_title($title);
        $normalized_title = strtolower(trim(remove_accents($title)));
        if ($content_type === 'category') {
            $term = get_term_by('slug', $slug, 'category');
            if ($term && !is_wp_error($term)) {
                $view_url = get_term_link($term);
                return array(
                    'entity_id' => intval($term->term_id),
                    'edit_url' => admin_url('term.php?taxonomy=category&tag_ID=' . intval($term->term_id) . '&post_type=post'),
                    'view_url' => is_wp_error($view_url) ? '' : (string) $view_url,
                );
            }

            $matching_terms = get_terms(array(
                'taxonomy' => 'category',
                'hide_empty' => false,
                'name__like' => $title,
                'number' => 20,
            ));
            if (!is_wp_error($matching_terms) && !empty($matching_terms)) {
                foreach ($matching_terms as $candidate_term) {
                    if (!($candidate_term instanceof WP_Term)) {
                        continue;
                    }
                    $candidate_name = strtolower(trim(remove_accents((string) $candidate_term->name)));
                    if ($candidate_name !== $normalized_title) {
                        continue;
                    }
                    $view_url = get_term_link($candidate_term);
                    return array(
                        'entity_id' => intval($candidate_term->term_id),
                        'edit_url' => admin_url('term.php?taxonomy=category&tag_ID=' . intval($candidate_term->term_id) . '&post_type=post'),
                        'view_url' => is_wp_error($view_url) ? '' : (string) $view_url,
                    );
                }
            }
            return null;
        }

        $posts = get_posts(array(
            'post_type' => $content_type,
            'name' => $slug,
            'post_status' => array('draft', 'publish', 'pending', 'future', 'private'),
            'posts_per_page' => 1,
            'fields' => 'ids',
            'orderby' => 'date',
            'order' => 'DESC',
            // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters -- Canonical entity resolution must find the existing post across language filters before considering a new write; one ID is requested.
            'suppress_filters' => true,
        ));
        if (empty($posts)) {
            global $wpdb;
            $post_type_sql = esc_sql($content_type);
            $normalized_sql = '%' . $wpdb->esc_like($normalized_title) . '%';
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $row = $wpdb->get_row($wpdb->prepare(
                "SELECT ID, post_title FROM {$wpdb->posts}
                 WHERE post_type = %s
                   AND post_status IN ('draft','publish','pending','future','private')
                   AND post_title <> ''
                   AND LOWER(TRIM(post_title)) <> ''
                 ORDER BY ID DESC",
                $post_type_sql
            ));
            if (!$row) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $candidate_rows = $wpdb->get_results($wpdb->prepare(
                    "SELECT ID, post_title FROM {$wpdb->posts}
                     WHERE post_type = %s
                       AND post_status IN ('draft','publish','pending','future','private')
                       AND post_title LIKE %s
                     ORDER BY ID DESC
                     LIMIT 20",
                    $post_type_sql,
                    '%' . $wpdb->esc_like($title) . '%'
                ));
            } else {
                $candidate_rows = array($row);
            }
            if (!empty($candidate_rows)) {
                foreach ($candidate_rows as $candidate_row) {
                    $candidate_title = strtolower(trim(remove_accents((string) ($candidate_row->post_title ?? ''))));
                    if ($candidate_title !== $normalized_title) {
                        continue;
                    }
                    $entity_id = intval($candidate_row->ID ?? 0);
                    if ($entity_id > 0) {
                        return array(
                            'entity_id' => $entity_id,
                            'edit_url' => (string) get_edit_post_link($entity_id, 'raw'),
                            'view_url' => (string) get_permalink($entity_id),
                        );
                    }
                }
            }
            return null;
        }
        $entity_id = intval($posts[0]);
        return array(
            'entity_id' => $entity_id,
            'edit_url' => (string) get_edit_post_link($entity_id, 'raw'),
            'view_url' => (string) get_permalink($entity_id),
        );
    }

    private function aysa_adjustments_build_content_strategy_generation_message($language, $strategy_type, $saved, $existing, $errors, $total, $published, $preview_lines = array(), $preview_mode = '') {
        $strategy_label = $this->aysa_adjustments_content_strategy_type_label((string) $strategy_type, (string) $language);
        $preview_lines = is_array($preview_lines) ? array_values(array_filter(array_map('strval', $preview_lines))) : array();
        $preview_mode = sanitize_key((string) $preview_mode);
        $preview_intro = '';
        if (!empty($preview_lines)) {
            $preview_intro = match ($preview_mode) {
                'saved' => match ($language) {
                    'ro' => 'Iată primele idei noi salvate:',
                    'bg' => 'Ето първите нови запазени идеи:',
                    default => 'Here are the first new ideas that were saved:',
                },
                'existing' => match ($language) {
                    'ro' => 'Iată ideile propuse acum, dar recunoscute ca fiind deja acoperite în plan:',
                    'bg' => 'Ето идеите, предложени сега, но разпознати като вече покрити в плана:',
                    default => 'Here are the ideas proposed now, but already covered by the plan:',
                },
                default => '',
            };
        }
        $preview_block = ($preview_intro !== '' ? "\n\n" . $preview_intro : '') . ($preview_lines ? "\n\n- " . implode("\n- ", $preview_lines) : '');

        return match ($language) {
            'ro' => 'Am generat strategia de conținut de tip ' . $strategy_label . '. Am salvat ' . intval($saved) . ' idei noi, ' . intval($existing) . ' existau deja, iar ' . intval($errors) . ' nu au putut fi salvate. În total, planul tău are acum ' . intval($total) . ' itemi, dintre care ' . intval($published) . ' sunt deja legați de pagini.' .
                ($preview_block !== '' ? $preview_block : ''),
            'bg' => 'Генерирах content стратегия от типа ' . $strategy_label . '. Запазих ' . intval($saved) . ' нови идеи, ' . intval($existing) . ' вече съществуваха, а ' . intval($errors) . ' не можаха да бъдат запазени. Общо планът ти сега има ' . intval($total) . ' елемента, от които ' . intval($published) . ' вече са свързани със страници.' .
                ($preview_block !== '' ? $preview_block : ''),
            default => 'I generated the ' . $strategy_label . ' content strategy. I saved ' . intval($saved) . ' new ideas, ' . intval($existing) . ' were already there, and ' . intval($errors) . ' could not be saved. Your plan now has ' . intval($total) . ' items in total, of which ' . intval($published) . ' are already tied to pages.' .
                ($preview_block !== '' ? $preview_block : ''),
        };
    }

    private function aysa_adjustments_build_content_strategy_preview_lines($items, $language, $limit = 5) {
        $items = is_array($items) ? $items : array();
        $limit = max(1, intval($limit));
        $lines = array();

        foreach ($items as $item) {
            if (!is_array($item)) {
                continue;
            }
            $title = sanitize_text_field((string) ($item['title'] ?? ''));
            if ($title === '') {
                continue;
            }

            $keywords = array();
            $raw_keywords = $item['target_keywords'] ?? $item['TargetKeywords'] ?? $item['targetKeywords'] ?? array();
            if (is_array($raw_keywords)) {
                foreach ($raw_keywords as $keyword) {
                    $keyword = sanitize_text_field((string) $keyword);
                    if ($keyword !== '' && !in_array($keyword, $keywords, true)) {
                        $keywords[] = $keyword;
                    }
                    if (count($keywords) >= 3) {
                        break;
                    }
                }
            }

            $associated_page = sanitize_text_field((string) ($item['associated_page'] ?? $item['associatedPage'] ?? ''));
            $parts = array($title);
            if (!empty($keywords)) {
                $parts[] = match ($language) {
                    'ro' => 'keyword-uri: ' . implode(', ', $keywords),
                    'bg' => 'keywords: ' . implode(', ', $keywords),
                    default => 'keywords: ' . implode(', ', $keywords),
                };
            }
            if ($associated_page !== '') {
                $parts[] = match ($language) {
                    'ro' => 'pagină: ' . $associated_page,
                    'bg' => 'страница: ' . $associated_page,
                    default => 'page: ' . $associated_page,
                };
            }
            $lines[] = implode(' | ', $parts);
            if (count($lines) >= $limit) {
                break;
            }
        }

        return $lines;
    }

    private function aysa_adjustments_content_strategy_type_label($strategy_type, $language) {
        $strategy_type = sanitize_key((string) $strategy_type);
        return match ($strategy_type) {
            'cluster_titles' => 'Cluster-based / Keyword clusters',
            'competitor_derived_titles' => 'Competitor analysis',
            'intent_layering' => 'Intent layering',
            'seasonal_event' => 'Seasonal and events',
            'serp_features' => 'SERP features',
            'evergreen_fresh_mix' => 'Evergreen and fresh',
            'customer_journey' => 'Customer journey',
            'local_seo' => 'Local SEO',
            'authority_building' => 'Authority building',
            'persona_based' => 'Persona-based',
            default => $strategy_type,
        };
    }

    private function aysa_adjustments_build_keyword_workspace_artifact($snapshot, $language = 'en') {
        if (!is_array($snapshot)) {
            return array();
        }
        $language = in_array($language, array('en', 'ro', 'bg'), true) ? $language : 'en';
        $gsc_count = intval($snapshot['gsc_count'] ?? 0);
        $suggestions_count = intval($snapshot['suggestions_count'] ?? 0);
        $total_terms = $gsc_count + $suggestions_count;
        $status = isset($snapshot['refresh_status']) && is_array($snapshot['refresh_status']) ? $snapshot['refresh_status'] : array();
        $seed_keywords = isset($snapshot['seed_keywords']) && is_array($snapshot['seed_keywords']) ? $snapshot['seed_keywords'] : array();
        $gsc_rows = isset($snapshot['gsc_rows']) && is_array($snapshot['gsc_rows']) ? $snapshot['gsc_rows'] : array();
        $suggestion_rows = isset($snapshot['suggestion_rows']) && is_array($snapshot['suggestion_rows']) ? $snapshot['suggestion_rows'] : array();
        $competitor_gap_rows = isset($snapshot['competitor_gap_rows']) && is_array($snapshot['competitor_gap_rows']) ? $snapshot['competitor_gap_rows'] : array();
        $unified_rows = isset($snapshot['unified_rows']) && is_array($snapshot['unified_rows']) ? $snapshot['unified_rows'] : array();
        $association_rows = isset($snapshot['association_rows']) && is_array($snapshot['association_rows']) ? $snapshot['association_rows'] : array();
        $recent_relevant = $this->aysa_adjustments_get_recent_relevant_keywords(intval($snapshot['project_id'] ?? 0));
        $recent_selected = isset($recent_relevant['selected_keywords']) && is_array($recent_relevant['selected_keywords']) ? $recent_relevant['selected_keywords'] : array();
        $recent_selected_lookup = array_fill_keys(array_map(array($this, 'aysa_adjustments_normalize_keyword_lookup_key'), $recent_selected), true);

        if ($gsc_count <= 0 && $suggestions_count <= 0 && (($status['status'] ?? '') !== 'running')) {
            return array();
        }

        $mapped_pages = array();
        foreach ($association_rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $association_value = trim((string) ($row['value'] ?? ''));
            if ($association_value !== '') {
                $mapped_pages[$association_value] = true;
            }
        }
        foreach (array_merge($gsc_rows, $suggestion_rows) as $row) {
            if (!is_array($row)) {
                continue;
            }
            $target_url = trim((string) ($row['target_url'] ?? $row['url'] ?? ''));
            if ($target_url !== '') {
                $mapped_pages[$target_url] = true;
            }
        }
        $mapped_pages_count = count($mapped_pages);
        $seed_count = count($seed_keywords);
        $keyword_map_status = $total_terms > 0 ? 'ready' : 'pending';
        if (($status['status'] ?? '') === 'running') {
            $keyword_map_status = 'building';
        }

        $artifact = array(
            'type' => 'keyword_map',
            'status' => ($status['status'] ?? 'idle'),
            'title' => $this->aysa_adjustments_tr($language, 'Keyword research overview', 'Overview keyword research', 'Преглед на keyword research'),
            'summary' => '',
            'summary_cards' => array(
                array(
                    'title' => $this->aysa_adjustments_tr($language, 'Already visible in Google', 'Vizibilitate deja existentă în Google', 'Вече налична видимост в Google'),
                    'value' => (string) $gsc_count,
                    'detail' => $this->aysa_adjustments_tr(
                        $language,
                        ($gsc_count > 0
                            ? ($mapped_pages_count > 0 ? $mapped_pages_count . ' pages already connected to real Google search data' : 'Real Google queries are available and ready to be matched to pages')
                            : 'Waiting for confirmed Google ranking rows'),
                        ($gsc_count > 0
                            ? ($mapped_pages_count > 0 ? $mapped_pages_count . ' pagini deja conectate la date reale din Google' : 'Query-uri reale din Google sunt disponibile și pregătite pentru asociere pe pagini')
                            : 'Aștept rânduri confirmate de ranking din Google'),
                        ($gsc_count > 0
                            ? ($mapped_pages_count > 0 ? $mapped_pages_count . ' страници вече са свързани с реални данни от Google' : 'Налични са реални Google query-и и могат да бъдат свързани със страници')
                            : 'Изчаквам потвърдени Google ranking редове')
                    ),
                    'tone' => $gsc_count > 0 ? 'positive' : 'neutral',
                ),
                array(
                    'title' => $this->aysa_adjustments_tr($language, 'New keyword opportunities', 'Oportunități noi de keyword-uri', 'Нови keyword opportunities'),
                    'value' => (string) $suggestions_count,
                    'detail' => $this->aysa_adjustments_tr(
                        $language,
                        ($seed_count > 0 ? 'Built from ' . $seed_count . ' saved setup topics' : 'Built from the saved project context'),
                        ($seed_count > 0 ? 'Construite din ' . $seed_count . ' teme salvate în setup' : 'Construite din contextul deja salvat al proiectului'),
                        ($seed_count > 0 ? 'Изградени от ' . $seed_count . ' запазени теми в setup' : 'Изградени от запазения проектен контекст')
                    ),
                    'tone' => $suggestions_count > 0 ? 'positive' : 'neutral',
                ),
                array(
                    'title' => $this->aysa_adjustments_tr($language, 'Keyword map in progress', 'Keyword map în lucru', 'Keyword map в процес'),
                    'value' => (string) max($total_terms, count($unified_rows)),
                    'detail' => $this->aysa_adjustments_tr(
                        $language,
                        ($mapped_pages_count > 0 ? $mapped_pages_count . ' pages already have direct ranking signals to anchor the map' : 'The map is being prepared from the terms collected so far'),
                        ($mapped_pages_count > 0 ? $mapped_pages_count . ' pagini au deja semnale directe care ancorează harta' : 'Harta se pregătește din termenii colectați până acum'),
                        ($mapped_pages_count > 0 ? $mapped_pages_count . ' страници вече имат директни сигнали, които закотвят картата' : 'Картата се подготвя от събраните досега термини')
                    ),
                    'tone' => $keyword_map_status === 'ready' ? 'accent' : 'neutral',
                ),
                array(
                    'title' => $this->aysa_adjustments_tr($language, 'Next recommended step', 'Următorul pas recomandat', 'Следваща препоръчана стъпка'),
                    'value' => $this->aysa_adjustments_tr($language, 'Competitors', 'Competitori', 'Конкуренти'),
                    'detail' => $this->aysa_adjustments_tr(
                        $language,
                        'After the map is clear, I can expand the competitor set and compare gaps.',
                        'După ce clarificăm harta, pot extinde lista de competitori și compara golurile.',
                        'След като картата е ясна, мога да разширя списъка с конкуренти и да сравня липсите.'
                    ),
                    'tone' => 'neutral',
                ),
            ),
            'workflow_steps' => array(
                array(
                    'title' => $this->aysa_adjustments_tr($language, 'Extracting the keywords you already rank for', 'Extrag cuvintele pentru care rankezi deja', 'Извличам keyword-ите, за които вече rank-ваш'),
                    'status' => $gsc_count > 0 ? 'done' : ((($status['status'] ?? '') === 'running') ? 'running' : 'pending'),
                    'detail' => $gsc_count > 0
                        ? $this->aysa_adjustments_tr($language, 'I found ' . $gsc_count . ' real keywords already visible in Google.', 'Am găsit ' . $gsc_count . ' keyword-uri reale deja vizibile în Google.', 'Намерих ' . $gsc_count . ' реални keyword-а, които вече се виждат в Google.')
                        : $this->aysa_adjustments_tr($language, 'I am gathering the searches that already bring your site into Google.', 'Adun căutările care îți aduc deja site-ul în Google.', 'Събирам търсенията, които вече водят сайта ти в Google.'),
                ),
                array(
                    'title' => $this->aysa_adjustments_tr($language, 'Extracting new suggestions from your topics', 'Extrag sugestii noi din temele tale', 'Извличам нови предложения от твоите теми'),
                    'status' => $suggestions_count > 0 ? 'done' : 'pending',
                    'detail' => $suggestions_count > 0
                        ? $this->aysa_adjustments_tr($language, 'I generated ' . $suggestions_count . ' new keyword suggestions from the saved topics.', 'Am generat ' . $suggestions_count . ' sugestii noi de keyword-uri din temele salvate.', 'Генерирах ' . $suggestions_count . ' нови keyword предложения от запазените теми.')
                        : $this->aysa_adjustments_tr($language, 'I am extracting new suggestions based on the keywords you already gave me.', 'Extrag sugestii noi pe baza keyword-urilor pe care mi le-ai dat deja.', 'Извличам нови предложения на база keyword-ите, които вече ми даде.'),
                ),
                array(
                    'title' => $this->aysa_adjustments_tr($language, 'Matching the extracted keywords to your pages', 'Asociez keyword-urile extrase cu paginile tale', 'Свързвам извлечените keyword-и с твоите страници'),
                    'status' => $keyword_map_status,
                    'detail' => $this->aysa_adjustments_tr(
                        $language,
                        ($mapped_pages_count > 0 ? 'I already have direct matches for ' . $mapped_pages_count . ' pages.' : 'I am deciding which extracted keywords belong on each page.'),
                        ($mapped_pages_count > 0 ? 'Am deja asocieri directe pentru ' . $mapped_pages_count . ' pagini.' : 'Decid ce keyword-uri extrase se potrivesc pe fiecare pagină.'),
                        ($mapped_pages_count > 0 ? 'Вече имам директни съвпадения за ' . $mapped_pages_count . ' страници.' : 'Определям кои извлечени keyword-и пасват на всяка страница.')
                    ),
                ),
                array(
                    'title' => $this->aysa_adjustments_tr($language, 'Preparing the next step: competitors', 'Pregătesc pasul următor: competitori', 'Подготвям следващата стъпка: конкуренти'),
                    'status' => 'upcoming',
                    'detail' => $this->aysa_adjustments_tr(
                        $language,
                        'After the keyword map is clear, I will move to competitor discovery.',
                        'După ce harta de keyword-uri e clară, trec la descoperirea competitorilor.',
                        'След като keyword картата е ясна, ще премина към откриване на конкуренти.'
                    ),
                ),
            ),
            'sections' => array(),
            'cta_label' => $this->aysa_adjustments_tr($language, 'Open keyword map', 'Deschide keyword map', 'Отвори keyword map'),
        );

        if (($status['status'] ?? '') === 'running' && $gsc_count <= 0) {
            $artifact['summary'] = $this->aysa_adjustments_tr(
                $language,
                'Google ranking data is being collected in the background. Existing keyword suggestions are already available below.',
                'Datele de ranking din Google se colectează acum în fundal. Sugestiile de keyword-uri deja disponibile sunt afișate mai jos.',
                'Данните за ranking от Google се събират във фонов режим. Наличните keyword suggestions вече са показани по-долу.'
            );
        } else {
            $artifact['summary'] = $this->aysa_adjustments_tr(
                $language,
                'This view is organized from the real data already stored in Keyword Research.',
                'Această vedere este organizată din datele reale deja stocate în Keyword Research.',
                'Този изглед е организиран от реалните данни, които вече са налични в Keyword Research.'
            );
        }

        $unified_table_rows = array();
        foreach ($unified_rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keyword = trim((string) ($row['keyword'] ?? ''));
            if ($keyword === '') {
                continue;
            }
            $unified_table_rows[] = array(
                'keyword' => $keyword,
                'sources' => (string) ($row['sources'] ?? ''),
                'selected' => (string) ($row['selected'] ?? 'Available'),
                'volume' => (string) ($row['search_volume'] ?? ''),
                'page' => trim((string) ($row['page'] ?? '')),
                'group' => trim((string) ($row['group'] ?? '')),
            );
        }
        if (!empty($unified_table_rows)) {
            $artifact['sections'][] = array(
                'id' => 'unified_keywords',
                'kind' => 'table',
                'title' => $this->aysa_adjustments_tr($language, 'Unified keyword map', 'Keyword map unificat', 'Обединен keyword map'),
                'summary' => $this->aysa_adjustments_tr(
                    $language,
                    count($unified_table_rows) . ' deduplicated keywords combined from Google, suggestions, and selected competitor gap terms. ' . count($recent_selected_lookup) . ' are in the current relevant set.',
                    count($unified_table_rows) . ' keyword-uri deduplicate combinate din Google, suggestions și termenii selectați din competitor gap. ' . count($recent_selected_lookup) . ' sunt în setul relevant curent.',
                    count($unified_table_rows) . ' дедупликирани keyword-а, комбинирани от Google, suggestions и избраните competitor gap термини. ' . count($recent_selected_lookup) . ' са в текущия релевантен набор.'
                ),
                'button_label' => $this->aysa_adjustments_tr($language, 'See unified keywords', 'Vezi keyword-urile unificate', 'Виж обединените keyword-и'),
                'filter_placeholder' => $this->aysa_adjustments_tr($language, 'Filter keywords, sources, groups, or pages', 'Filtrează keyword-uri, surse, grupuri sau pagini', 'Филтрирай keyword-и, източници, групи или страници'),
                'empty_label' => $this->aysa_adjustments_tr($language, 'No unified keyword rows are available yet.', 'Nu există încă rânduri unificate.', 'Все още няма налични обединени редове.'),
                'columns' => array(
                    array('key' => 'keyword', 'label' => $this->aysa_adjustments_tr($language, 'Keyword', 'Keyword', 'Keyword')),
                    array('key' => 'sources', 'label' => $this->aysa_adjustments_tr($language, 'Sources', 'Surse', 'Източници')),
                    array('key' => 'selected', 'label' => $this->aysa_adjustments_tr($language, 'Status', 'Status', 'Статус')),
                    array('key' => 'volume', 'label' => $this->aysa_adjustments_tr($language, 'Volume', 'Volum', 'Обем')),
                    array('key' => 'group', 'label' => $this->aysa_adjustments_tr($language, 'Mapped group', 'Grup mapat', 'Свързана група')),
                    array('key' => 'page', 'label' => $this->aysa_adjustments_tr($language, 'Page', 'Pagină', 'Страница')),
                ),
                'rows' => $unified_table_rows,
            );
        }

        $gsc_table_rows = array();
        foreach ($gsc_rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keyword = trim((string) ($row['keyword'] ?? ''));
            if ($keyword === '') {
                continue;
            }
            $gsc_table_rows[] = array(
                'keyword' => $keyword,
                'volume' => (string) ($row['search_volume'] ?? $row['searchVolume'] ?? ''),
                'position' => (string) ($row['position'] ?? ''),
                'traffic' => (string) ($row['traffic'] ?? ''),
                'page' => trim((string) ($row['target_url'] ?? $row['url'] ?? '')),
            );
        }
        if (!empty($gsc_table_rows)) {
            $artifact['sections'][] = array(
                'id' => 'gsc_keywords',
                'kind' => 'table',
                'title' => $this->aysa_adjustments_tr($language, 'Keywords already visible in Google', 'Keyword-uri deja vizibile în Google', 'Keyword-и, които вече се виждат в Google'),
                'summary' => $this->aysa_adjustments_tr(
                    $language,
                    count($gsc_table_rows) . ' real rows currently available from Google visibility data.',
                    count($gsc_table_rows) . ' rânduri reale disponibile acum din datele de vizibilitate Google.',
                    count($gsc_table_rows) . ' реални реда, налични в момента от данните за видимост в Google.'
                ),
                'button_label' => $this->aysa_adjustments_tr($language, 'See Google keywords', 'Vezi keyword-urile din Google', 'Виж keyword-ите от Google'),
                'filter_placeholder' => $this->aysa_adjustments_tr($language, 'Filter Google keywords or pages', 'Filtrează keyword-uri Google sau pagini', 'Филтрирай Google keyword-и или страници'),
                'empty_label' => $this->aysa_adjustments_tr($language, 'No Google keyword rows are available yet.', 'Nu există încă rânduri Google disponibile.', 'Все още няма налични Google keyword редове.'),
                'columns' => array(
                    array('key' => 'keyword', 'label' => $this->aysa_adjustments_tr($language, 'Keyword', 'Keyword', 'Keyword')),
                    array('key' => 'volume', 'label' => $this->aysa_adjustments_tr($language, 'Volume', 'Volum', 'Обем')),
                    array('key' => 'position', 'label' => $this->aysa_adjustments_tr($language, 'Position', 'Poziție', 'Позиция')),
                    array('key' => 'traffic', 'label' => $this->aysa_adjustments_tr($language, 'Traffic', 'Trafic', 'Трафик')),
                    array('key' => 'page', 'label' => $this->aysa_adjustments_tr($language, 'Page', 'Pagină', 'Страница')),
                ),
                'rows' => $gsc_table_rows,
            );
        }

        $suggestion_table_rows = array();
        foreach ($suggestion_rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keyword = trim((string) ($row['keyword'] ?? ''));
            if ($keyword === '') {
                continue;
            }
            $suggestion_table_rows[] = array(
                'keyword' => $keyword,
                'volume' => (string) ($row['search_volume'] ?? $row['searchVolume'] ?? ''),
                'difficulty' => (string) ($row['difficulty'] ?? ''),
                'source' => (string) ($row['source'] ?? $this->aysa_adjustments_tr($language, 'New suggestion', 'Sugestie nouă', 'Ново предложение')),
                'page' => trim((string) ($row['target_url'] ?? $row['url'] ?? '')),
            );
        }
        if (!empty($suggestion_table_rows)) {
            $artifact['sections'][] = array(
                'id' => 'keyword_suggestions',
                'kind' => 'table',
                'title' => $this->aysa_adjustments_tr($language, 'Keyword suggestions', 'Sugestii de keyword-uri', 'Keyword предложения'),
                'summary' => $this->aysa_adjustments_tr(
                    $language,
                    count($suggestion_table_rows) . ' suggestion rows currently available from the saved setup topics.',
                    count($suggestion_table_rows) . ' rânduri de sugestii disponibile acum din temele salvate în setup.',
                    count($suggestion_table_rows) . ' реда с предложения, налични в момента от темите, запазени в setup.'
                ),
                'button_label' => $this->aysa_adjustments_tr($language, 'See keywords', 'Vezi keyword-urile', 'Виж keyword-ите'),
                'filter_placeholder' => $this->aysa_adjustments_tr($language, 'Filter suggestions or pages', 'Filtrează sugestii sau pagini', 'Филтрирай предложения или страници'),
                'empty_label' => $this->aysa_adjustments_tr($language, 'No keyword suggestions are available yet.', 'Nu există încă sugestii disponibile.', 'Все още няма налични предложения.'),
                'columns' => array(
                    array('key' => 'keyword', 'label' => $this->aysa_adjustments_tr($language, 'Keyword', 'Keyword', 'Keyword')),
                    array('key' => 'volume', 'label' => $this->aysa_adjustments_tr($language, 'Volume', 'Volum', 'Обем')),
                    array('key' => 'difficulty', 'label' => $this->aysa_adjustments_tr($language, 'Difficulty', 'Dificultate', 'Трудност')),
                    array('key' => 'source', 'label' => $this->aysa_adjustments_tr($language, 'Type', 'Tip', 'Тип')),
                    array('key' => 'page', 'label' => $this->aysa_adjustments_tr($language, 'Page', 'Pagină', 'Страница')),
                ),
                'rows' => $suggestion_table_rows,
            );
        }

        $page_rows = array();
        if (!empty($association_rows)) {
            $grouped_associations = $this->aysa_adjustments_group_associations_for_workspace($association_rows);
            $source_map = array();
            foreach ($unified_rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $source_map[$this->aysa_adjustments_normalize_keyword_lookup_key((string) ($row['keyword'] ?? ''))] = (string) ($row['sources'] ?? '');
            }
            foreach ($grouped_associations as $group) {
                if (!is_array($group)) {
                    continue;
                }
                $keywords = isset($group['keywords']) && is_array($group['keywords']) ? $group['keywords'] : array();
                if (empty($keywords)) {
                    continue;
                }
                if (!empty($recent_selected_lookup)) {
                    $keywords = array_values(array_filter($keywords, function($keyword) use ($recent_selected_lookup) {
                        return !empty($recent_selected_lookup[$this->aysa_adjustments_normalize_keyword_lookup_key($keyword)]);
                    }));
                    if (empty($keywords)) {
                        continue;
                    }
                }
                $source_labels = array();
                foreach ($keywords as $keyword) {
                    $source_string = (string) ($source_map[$this->aysa_adjustments_normalize_keyword_lookup_key($keyword)] ?? '');
                    foreach (array_filter(array_map('trim', explode(',', $source_string))) as $label) {
                        $source_labels[$label] = true;
                    }
                }
                $page_rows[] = array(
                    'keywords' => implode(', ', array_slice($keywords, 0, 8)),
                    'count' => (string) count($keywords),
                    'type' => (string) ($group['type'] ?? ''),
                    'entity' => (string) ($group['entity'] ?? ''),
                    'sources' => implode(', ', array_keys($source_labels)),
                    'page' => (string) ($group['page'] ?? ''),
                );
            }
        } else {
            $page_matches = array();
            foreach (array_merge($gsc_rows, $suggestion_rows) as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $target_url = trim((string) ($row['target_url'] ?? ''));
                $keyword = trim((string) ($row['keyword'] ?? ''));
                if ($target_url === '' || $keyword === '') {
                    continue;
                }
                if (!isset($page_matches[$target_url])) {
                    $page_matches[$target_url] = array();
                }
                $page_matches[$target_url][$keyword] = true;
            }
            foreach ($page_matches as $page_url => $keywords_map) {
                $keywords = array_keys($keywords_map);
                sort($keywords);
                $page_rows[] = array(
                    'keyword' => implode(', ', array_slice($keywords, 0, 6)),
                    'type' => $this->aysa_adjustments_tr($language, 'Page', 'Pagină', 'Страница'),
                    'entity' => $page_url,
                    'page' => $page_url,
                );
            }
        }

        if (!empty($page_rows)) {
            $artifact['sections'][] = array(
                'id' => 'page_matches',
                'kind' => 'table',
                'title' => $this->aysa_adjustments_tr($language, 'Keyword-to-page matches', 'Asocieri keyword-uri cu pagini', 'Свързвания на keyword-и към страници'),
                'summary' => $this->aysa_adjustments_tr(
                    $language,
                    count($page_rows) . ' grouped page mappings are currently available from the stored research data.',
                    count($page_rows) . ' grupări de asocieri pe pagini sunt disponibile acum în datele de research stocate.',
                    count($page_rows) . ' групирани свързвания към страници са налични в момента в запазените research данни.'
                ),
                'button_label' => $this->aysa_adjustments_tr($language, 'See matches', 'Vezi asocierile', 'Виж свързванията'),
                'filter_placeholder' => $this->aysa_adjustments_tr($language, 'Filter keywords, entities, or pages', 'Filtrează keyword-uri, entități sau pagini', 'Филтрирай keyword-и, обекти или страници'),
                'empty_label' => $this->aysa_adjustments_tr($language, 'No page matches are available yet.', 'Nu există încă asocieri pe pagini.', 'Все още няма свързвания по страници.'),
                'columns' => array(
                    array('key' => 'keywords', 'label' => $this->aysa_adjustments_tr($language, 'Keywords', 'Keyword-uri', 'Keyword-и')),
                    array('key' => 'count', 'label' => $this->aysa_adjustments_tr($language, 'Count', 'Număr', 'Брой')),
                    array('key' => 'type', 'label' => $this->aysa_adjustments_tr($language, 'Type', 'Tip', 'Тип')),
                    array('key' => 'entity', 'label' => $this->aysa_adjustments_tr($language, 'Entity', 'Entitate', 'Обект')),
                    array('key' => 'sources', 'label' => $this->aysa_adjustments_tr($language, 'Sources', 'Surse', 'Източници')),
                    array('key' => 'page', 'label' => $this->aysa_adjustments_tr($language, 'Matched page', 'Pagină asociată', 'Свързана страница')),
                ),
                'rows' => $page_rows,
            );
        }

        return $artifact;
    }

    private function aysa_adjustments_build_research_workspace_artifact($snapshot, $language = 'en') {
        $artifact = $this->aysa_adjustments_build_keyword_workspace_artifact($snapshot, $language);
        $language = in_array($language, array('en', 'ro', 'bg'), true) ? $language : 'en';
        $modules = isset($snapshot['modules']) && is_array($snapshot['modules']) ? $snapshot['modules'] : array();
        if ($artifact === array()) {
            $artifact = array(
                'type' => 'research_analysis',
                'status' => 'ready',
                'title' => $this->aysa_adjustments_tr($language, 'Research & Analysis', 'Research & Analysis', 'Research & Analysis'),
                'summary' => $this->aysa_adjustments_tr($language, 'I organized the current research data by module.', 'Am organizat datele actuale de research pe module.', 'Организирах текущите research данни по модули.'),
                'sections' => array(),
                'cta_label' => $this->aysa_adjustments_tr($language, 'Open research data', 'Deschide datele de research', 'Отвори research данните'),
            );
        } else {
            $artifact['type'] = 'research_analysis';
            $artifact['title'] = $this->aysa_adjustments_tr($language, 'Research & Analysis', 'Research & Analysis', 'Research & Analysis');
            $artifact['cta_label'] = $this->aysa_adjustments_tr($language, 'Open research data', 'Deschide datele de research', 'Отвори research данните');
        }

        $extra_sections = array();
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'monitoring_status',
            $this->aysa_adjustments_tr($language, 'Ranking monitoring status', 'Status monitorizare ranking', 'Статус на ranking monitoring'),
            $this->aysa_adjustments_tr($language, 'Current keyword tracking totals and background refresh status.', 'Totalurile curente pentru keyword tracking și statusul refresh-ului în background.', 'Текущите totals за keyword tracking и статусът на background refresh-а.'),
            $this->aysa_adjustments_tr($language, 'Filter status rows', 'Filtrează statusul', 'Филтрирай status редовете'),
            isset($modules['monitoring_ranking']['status_rows']) ? $modules['monitoring_ranking']['status_rows'] : array(),
            array(
                array('key' => 'metric', 'label' => $this->aysa_adjustments_tr($language, 'Metric', 'Metrică', 'Метрика')),
                array('key' => 'value', 'label' => $this->aysa_adjustments_tr($language, 'Value', 'Valoare', 'Стойност')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'monitoring_ranking',
            $this->aysa_adjustments_tr($language, 'Ranking monitoring', 'Monitorizare ranking', 'Ranking monitoring'),
            $this->aysa_adjustments_tr($language, 'Latest local rank rows saved for tracked keywords.', 'Ultimele rânduri locale de ranking salvate pentru keywordurile urmărite.', 'Последните локални ranking редове, запазени за следените keyword-и.'),
            $this->aysa_adjustments_tr($language, 'Filter ranking rows', 'Filtrează rândurile', 'Филтрирай ranking редовете'),
            isset($modules['monitoring_ranking']['rows']) ? $modules['monitoring_ranking']['rows'] : array(),
            array(
                array('key' => 'keyword', 'label' => $this->aysa_adjustments_tr($language, 'Keyword', 'Keyword', 'Keyword')),
                array('key' => 'position', 'label' => $this->aysa_adjustments_tr($language, 'Position', 'Poziție', 'Позиция')),
                array('key' => 'previous_position', 'label' => $this->aysa_adjustments_tr($language, 'Prev. position', 'Poziție anterioară', 'Пред. позиция')),
                array('key' => 'position_delta', 'label' => $this->aysa_adjustments_tr($language, 'Delta', 'Diferență', 'Разлика')),
                array('key' => 'search_volume', 'label' => $this->aysa_adjustments_tr($language, 'Volume', 'Volum', 'Обем')),
                array('key' => 'traffic', 'label' => $this->aysa_adjustments_tr($language, 'Traffic', 'Trafic', 'Трафик')),
                array('key' => 'last_checked_at', 'label' => $this->aysa_adjustments_tr($language, 'Last checked', 'Ultima verificare', 'Последна проверка')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'monitoring_stale',
            $this->aysa_adjustments_tr($language, 'Stale tracking rows', 'Keyworduri stale', 'Stale keyword-и'),
            $this->aysa_adjustments_tr($language, 'Tracked keywords that still do not have a local rank snapshot.', 'Keyworduri urmărite care încă nu au un snapshot local de ranking.', 'Следени keyword-и, които все още нямат локален ranking snapshot.'),
            $this->aysa_adjustments_tr($language, 'Filter stale rows', 'Filtrează keywordurile stale', 'Филтрирай stale редовете'),
            isset($modules['monitoring_ranking']['stale_rows']) ? $modules['monitoring_ranking']['stale_rows'] : array(),
            array(
                array('key' => 'keyword', 'label' => $this->aysa_adjustments_tr($language, 'Keyword', 'Keyword', 'Keyword')),
                array('key' => 'associated_on', 'label' => $this->aysa_adjustments_tr($language, 'Association', 'Asociere', 'Асоциация')),
                array('key' => 'link', 'label' => $this->aysa_adjustments_tr($language, 'Target URL', 'URL țintă', 'Target URL')),
                array('key' => 'reason', 'label' => $this->aysa_adjustments_tr($language, 'Reason', 'Motiv', 'Причина')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'competitor_gap',
            $this->aysa_adjustments_tr($language, 'Competitor gap', 'Competitor gap', 'Competitor gap'),
            $this->aysa_adjustments_tr($language, 'Keywords competitors cover and you should review.', 'Keyword-uri pe care competitorii le acoperă și merită să le verifici.', 'Keyword-и, които конкурентите покриват и си струва да прегледаш.'),
            $this->aysa_adjustments_tr($language, 'Filter keywords', 'Filtrează keyword-uri', 'Филтрирай keyword-и'),
            isset($modules['competitor_gap']['rows']) ? $modules['competitor_gap']['rows'] : array(),
            array(
                array('key' => 'keyword', 'label' => $this->aysa_adjustments_tr($language, 'Keyword', 'Keyword', 'Keyword')),
                array('key' => 'search_volume', 'label' => $this->aysa_adjustments_tr($language, 'Volume', 'Volum', 'Обем')),
                array('key' => 'difficulty', 'label' => $this->aysa_adjustments_tr($language, 'Difficulty', 'Dificultate', 'Трудност')),
                array('key' => 'intent', 'label' => $this->aysa_adjustments_tr($language, 'Intent', 'Intenție', 'Intent')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'competitor_identification',
            $this->aysa_adjustments_tr($language, 'Competitor identification', 'Competitor identification', 'Competitor identification'),
            $this->aysa_adjustments_tr($language, 'Competitors identified from the seed keywords.', 'Competitori identificați pornind de la seed keywords.', 'Конкуренти, идентифицирани от seed keywords.'),
            $this->aysa_adjustments_tr($language, 'Filter competitors', 'Filtrează competitori', 'Филтрирай конкуренти'),
            isset($modules['competitor_identification']['rows']) ? $modules['competitor_identification']['rows'] : array(),
            array(
                array('key' => 'domain', 'label' => $this->aysa_adjustments_tr($language, 'Domain', 'Domeniu', 'Домейн')),
                array('key' => 'overlap_keywords', 'label' => $this->aysa_adjustments_tr($language, 'Overlap', 'Suprapunere', 'Припокриване')),
                array('key' => 'avg_position', 'label' => $this->aysa_adjustments_tr($language, 'Avg. Position', 'Poziție medie', 'Средна позиция')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'competitor_backlinks',
            $this->aysa_adjustments_tr($language, 'Competitor backlinks', 'Competitor backlinks', 'Competitor backlinks'),
            $this->aysa_adjustments_tr($language, 'Backlink and authority signals saved locally.', 'Semnale locale despre backlinks și autoritate.', 'Локално запазени сигнали за backlinks и authority.'),
            $this->aysa_adjustments_tr($language, 'Filter domains', 'Filtrează domenii', 'Филтрирай домейни'),
            isset($modules['competitor_backlinks']['rows']) ? $modules['competitor_backlinks']['rows'] : array(),
            array(
                array('key' => 'domain', 'label' => $this->aysa_adjustments_tr($language, 'Domain', 'Domeniu', 'Домейн')),
                array('key' => 'authority', 'label' => $this->aysa_adjustments_tr($language, 'Authority', 'Autoritate', 'Authority')),
                array('key' => 'backlinks', 'label' => $this->aysa_adjustments_tr($language, 'Backlinks', 'Backlink-uri', 'Backlinks')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'offpage_backlink_audit',
            $this->aysa_adjustments_tr($language, 'Backlink audit', 'Audit backlinkuri', 'Backlink audit'),
            $this->aysa_adjustments_tr($language, 'Backlink profile health, risks, and broken links already saved locally.', 'Starea profilului de backlinkuri, riscurile și linkurile rupte deja salvate local.', 'Състоянието на backlink профила, рисковете и счупените линкове, вече запазени локално.'),
            $this->aysa_adjustments_tr($language, 'Filter backlink rows', 'Filtrează backlinkurile', 'Филтрирай backlink редовете'),
            isset($modules['offpage_backlink_audit']['rows']) ? $modules['offpage_backlink_audit']['rows'] : array(),
            array(
                array('key' => 'referring_domain', 'label' => $this->aysa_adjustments_tr($language, 'Referring domain', 'Domeniu sursă', 'Препращащ домейн')),
                array('key' => 'source_url', 'label' => $this->aysa_adjustments_tr($language, 'Source URL', 'URL sursă', 'Source URL')),
                array('key' => 'target_url', 'label' => $this->aysa_adjustments_tr($language, 'Target URL', 'URL țintă', 'Target URL')),
                array('key' => 'anchor_text', 'label' => $this->aysa_adjustments_tr($language, 'Anchor', 'Anchor', 'Anchor')),
                array('key' => 'risk', 'label' => $this->aysa_adjustments_tr($language, 'Risk', 'Risc', 'Риск')),
                array('key' => 'health', 'label' => $this->aysa_adjustments_tr($language, 'Health', 'Stare', 'Състояние')),
                array('key' => 'issue_type', 'label' => $this->aysa_adjustments_tr($language, 'Issue', 'Problemă', 'Проблем')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'offpage_link_building_orders',
            $this->aysa_adjustments_tr($language, 'Link building orders', 'Comenzi link building', 'Link building поръчки'),
            $this->aysa_adjustments_tr($language, 'Orders already placed for off-page placements and sponsored articles.', 'Comenzile deja plasate pentru plasări off-page și articole sponsorizate.', 'Вече създадените поръчки за off-page публикации и спонсорирани статии.'),
            $this->aysa_adjustments_tr($language, 'Filter orders', 'Filtrează comenzile', 'Филтрирай поръчките'),
            isset($modules['offpage_link_building']['orders_rows']) ? $modules['offpage_link_building']['orders_rows'] : array(),
            array(
                array('key' => 'portal_domain', 'label' => $this->aysa_adjustments_tr($language, 'Portal', 'Portal', 'Портал')),
                array('key' => 'service_type', 'label' => $this->aysa_adjustments_tr($language, 'Service', 'Serviciu', 'Услуга')),
                array('key' => 'keyword', 'label' => $this->aysa_adjustments_tr($language, 'Keyword', 'Keyword', 'Keyword')),
                array('key' => 'target_url', 'label' => $this->aysa_adjustments_tr($language, 'Target URL', 'URL țintă', 'Target URL')),
                array('key' => 'status', 'label' => $this->aysa_adjustments_tr($language, 'Status', 'Status', 'Статус')),
                array('key' => 'verification', 'label' => $this->aysa_adjustments_tr($language, 'Verification', 'Verificare', 'Проверка')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'offpage_link_building_issues',
            $this->aysa_adjustments_tr($language, 'Link issues', 'Probleme de linkuri', 'Проблеми с линковете'),
            $this->aysa_adjustments_tr($language, 'Placements or links that need attention after publishing.', 'Plasări sau linkuri care au nevoie de atenție după publicare.', 'Публикации или линкове, които имат нужда от внимание след публикуване.'),
            $this->aysa_adjustments_tr($language, 'Filter issues', 'Filtrează problemele', 'Филтрирай проблемите'),
            isset($modules['offpage_link_building']['issues_rows']) ? $modules['offpage_link_building']['issues_rows'] : array(),
            array(
                array('key' => 'portal_domain', 'label' => $this->aysa_adjustments_tr($language, 'Portal', 'Portal', 'Портал')),
                array('key' => 'target_url', 'label' => $this->aysa_adjustments_tr($language, 'Target URL', 'URL țintă', 'Target URL')),
                array('key' => 'live_url', 'label' => $this->aysa_adjustments_tr($language, 'Live URL', 'URL live', 'Live URL')),
                array('key' => 'issue_type', 'label' => $this->aysa_adjustments_tr($language, 'Issue', 'Problemă', 'Проблем')),
                array('key' => 'status', 'label' => $this->aysa_adjustments_tr($language, 'Status', 'Status', 'Статус')),
                array('key' => 'message', 'label' => $this->aysa_adjustments_tr($language, 'Details', 'Detalii', 'Детайли')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'missing_pages',
            $this->aysa_adjustments_tr($language, 'Missing pages', 'Missing pages', 'Missing pages'),
            $this->aysa_adjustments_tr($language, 'Keywords and plans that still need a page.', 'Keyword-uri și planuri care încă au nevoie de o pagină.', 'Keyword-и и планове, които все още имат нужда от страница.'),
            $this->aysa_adjustments_tr($language, 'Filter missing pages', 'Filtrează lipsurile', 'Филтрирай липсите'),
            isset($modules['missing_pages']['rows']) ? $modules['missing_pages']['rows'] : array(),
            array(
                array('key' => 'keyword', 'label' => $this->aysa_adjustments_tr($language, 'Keyword', 'Keyword', 'Keyword')),
                array('key' => 'source', 'label' => $this->aysa_adjustments_tr($language, 'Source', 'Sursă', 'Източник')),
                array('key' => 'search_volume', 'label' => $this->aysa_adjustments_tr($language, 'Volume', 'Volum', 'Обем')),
                array('key' => 'target', 'label' => $this->aysa_adjustments_tr($language, 'Recommended page', 'Pagină recomandată', 'Препоръчана страница')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'content_strategy',
            $this->aysa_adjustments_tr($language, 'Content strategy', 'Content strategy', 'Content strategy'),
            $this->aysa_adjustments_tr($language, 'Existing content plans already saved in the project.', 'Planurile de content deja salvate în proiect.', 'Съществуващите content планове, вече запазени в проекта.'),
            $this->aysa_adjustments_tr($language, 'Filter content plans', 'Filtrează planurile', 'Филтрирай плановете'),
            isset($modules['content_strategy']['rows']) ? $modules['content_strategy']['rows'] : array(),
            array(
                array('key' => 'title', 'label' => $this->aysa_adjustments_tr($language, 'Title', 'Titlu', 'Заглавие')),
                array('key' => 'strategy_type', 'label' => $this->aysa_adjustments_tr($language, 'Type', 'Tip', 'Тип')),
                array('key' => 'status', 'label' => $this->aysa_adjustments_tr($language, 'Status', 'Status', 'Статус')),
                array('key' => 'stage', 'label' => $this->aysa_adjustments_tr($language, 'Stage', 'Etapă', 'Етап')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'onpage_optimization',
            $this->aysa_adjustments_tr($language, 'On-page optimization', 'On-page optimization', 'On-page optimization'),
            $this->aysa_adjustments_tr($language, 'Pages grouped by entity type, keyword focus, and optimization status.', 'Pagini grupate după tip de entitate, focusul de keyword și statusul optimizării.', 'Страници, групирани по тип обект, keyword фокус и статус на оптимизация.'),
            $this->aysa_adjustments_tr($language, 'Filter pages or keywords', 'Filtrează pagini sau keyword-uri', 'Филтрирай страници или keyword-и'),
            isset($modules['onpage_optimization']['rows']) ? $modules['onpage_optimization']['rows'] : array(),
            array(
                array('key' => 'entity_type', 'label' => $this->aysa_adjustments_tr($language, 'Entity type', 'Tip entitate', 'Тип обект')),
                array('key' => 'entity', 'label' => $this->aysa_adjustments_tr($language, 'Page', 'Pagină', 'Страница')),
                array('key' => 'main_keyword', 'label' => $this->aysa_adjustments_tr($language, 'Main keyword', 'Keyword principal', 'Основен keyword')),
                array('key' => 'all_keywords', 'label' => $this->aysa_adjustments_tr($language, 'All keywords', 'Toate keyword-urile', 'Всички keyword-и')),
                array('key' => 'optimized', 'label' => $this->aysa_adjustments_tr($language, 'Optimized', 'Optimizat', 'Оптимизирано')),
                array('key' => 'seo_score', 'label' => $this->aysa_adjustments_tr($language, 'SEO score', 'Scor SEO', 'SEO резултат')),
                array('key' => 'page', 'label' => $this->aysa_adjustments_tr($language, 'URL', 'URL', 'URL')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'technical_site_audit',
            $this->aysa_adjustments_tr($language, 'Site audit overview', 'Privire generală audit tehnic', 'Преглед на site audit-а'),
            $this->aysa_adjustments_tr($language, 'Overview of the current technical crawl.', 'Privire generală asupra crawl-ului tehnic curent.', 'Общ преглед на текущото техническо обхождане.'),
            $this->aysa_adjustments_tr($language, 'Filter metrics', 'Filtrează metricile', 'Филтрирай метриките'),
            isset($modules['technical_site_audit']['overview_rows']) ? $modules['technical_site_audit']['overview_rows'] : array(),
            array(
                array('key' => 'metric', 'label' => $this->aysa_adjustments_tr($language, 'Metric', 'Metrică', 'Метрика')),
                array('key' => 'value', 'label' => $this->aysa_adjustments_tr($language, 'Value', 'Valoare', 'Стойност')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'technical_site_audit_issues',
            $this->aysa_adjustments_tr($language, 'Site audit issues', 'Probleme audit tehnic', 'Проблеми от site audit-а'),
            $this->aysa_adjustments_tr($language, 'Issues found during the crawl.', 'Problemele găsite în timpul crawl-ului.', 'Проблемите, намерени по време на обхождането.'),
            $this->aysa_adjustments_tr($language, 'Filter issues', 'Filtrează problemele', 'Филтрирай проблемите'),
            isset($modules['technical_site_audit']['rows']) ? $modules['technical_site_audit']['rows'] : array(),
            array(
                array('key' => 'severity', 'label' => $this->aysa_adjustments_tr($language, 'Severity', 'Severitate', 'Тежест')),
                array('key' => 'title', 'label' => $this->aysa_adjustments_tr($language, 'Issue', 'Problemă', 'Проблем')),
                array('key' => 'affected_pages', 'label' => $this->aysa_adjustments_tr($language, 'Affected pages', 'Pagini afectate', 'Засегнати страници')),
                array('key' => 'description', 'label' => $this->aysa_adjustments_tr($language, 'Description', 'Descriere', 'Описание')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'technical_site_audit_pages',
            $this->aysa_adjustments_tr($language, 'Crawled pages', 'Pagini crawl-ate', 'Обходени страници'),
            $this->aysa_adjustments_tr($language, 'Pages included in the audit.', 'Paginile incluse în audit.', 'Страници, включени в одита.'),
            $this->aysa_adjustments_tr($language, 'Filter pages', 'Filtrează paginile', 'Филтрирай страниците'),
            isset($modules['technical_site_audit']['pages']) ? $modules['technical_site_audit']['pages'] : array(),
            array(
                array('key' => 'url', 'label' => $this->aysa_adjustments_tr($language, 'URL', 'URL', 'URL')),
                array('key' => 'title', 'label' => $this->aysa_adjustments_tr($language, 'Title', 'Titlu', 'Заглавие')),
                array('key' => 'status_code', 'label' => $this->aysa_adjustments_tr($language, 'Status', 'Status', 'Статус')),
                array('key' => 'indexability', 'label' => $this->aysa_adjustments_tr($language, 'Indexability', 'Indexabilitate', 'Индексация')),
                array('key' => 'crawl_depth', 'label' => $this->aysa_adjustments_tr($language, 'Depth', 'Adâncime', 'Дълбочина')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'technical_site_audit_files',
            $this->aysa_adjustments_tr($language, 'Audit exports', 'Exporturi audit', 'Експорти от audit-а'),
            $this->aysa_adjustments_tr($language, 'Saved audit files and exports.', 'Fișierele și exporturile salvate din audit.', 'Запазени файлове и експорти от одита.'),
            $this->aysa_adjustments_tr($language, 'Filter exports', 'Filtrează exporturile', 'Филтрирай експортите'),
            isset($modules['technical_site_audit']['files']) ? $modules['technical_site_audit']['files'] : array(),
            array(
                array('key' => 'filename', 'label' => $this->aysa_adjustments_tr($language, 'File', 'Fișier', 'Файл')),
                array('key' => 'type', 'label' => $this->aysa_adjustments_tr($language, 'Type', 'Tip', 'Тип')),
                array('key' => 'row_count', 'label' => $this->aysa_adjustments_tr($language, 'Rows', 'Rânduri', 'Редове')),
                array('key' => 'updated_at', 'label' => $this->aysa_adjustments_tr($language, 'Updated', 'Actualizat', 'Обновен')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'technical_pagespeed',
            $this->aysa_adjustments_tr($language, 'PageSpeed overview', 'Privire generală PageSpeed', 'Преглед на PageSpeed'),
            $this->aysa_adjustments_tr($language, 'Overview of the current Google PageSpeed summary.', 'Privire generală asupra rezumatului Google PageSpeed.', 'Общ преглед на текущото Google PageSpeed резюме.'),
            $this->aysa_adjustments_tr($language, 'Filter PageSpeed metrics', 'Filtrează metricile PageSpeed', 'Филтрирай PageSpeed метриките'),
            isset($modules['technical_pagespeed']['overview_rows']) ? $modules['technical_pagespeed']['overview_rows'] : array(),
            array(
                array('key' => 'metric', 'label' => $this->aysa_adjustments_tr($language, 'Metric', 'Metrică', 'Метрика')),
                array('key' => 'value', 'label' => $this->aysa_adjustments_tr($language, 'Value', 'Valoare', 'Стойност')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'technical_pagespeed_opportunities',
            $this->aysa_adjustments_tr($language, 'PageSpeed opportunities', 'Oportunități PageSpeed', 'PageSpeed възможности'),
            $this->aysa_adjustments_tr($language, 'The main opportunities reported by Google PageSpeed.', 'Principalele oportunități raportate de Google PageSpeed.', 'Основните възможности, отчетени от Google PageSpeed.'),
            $this->aysa_adjustments_tr($language, 'Filter opportunities', 'Filtrează oportunitățile', 'Филтрирай възможностите'),
            isset($modules['technical_pagespeed']['rows']) ? $modules['technical_pagespeed']['rows'] : array(),
            array(
                array('key' => 'strategy', 'label' => $this->aysa_adjustments_tr($language, 'Mode', 'Mod', 'Режим')),
                array('key' => 'title', 'label' => $this->aysa_adjustments_tr($language, 'Opportunity', 'Oportunitate', 'Възможност')),
                array('key' => 'display_value', 'label' => $this->aysa_adjustments_tr($language, 'Display value', 'Valoare afișată', 'Показвана стойност')),
                array('key' => 'impact', 'label' => $this->aysa_adjustments_tr($language, 'Impact', 'Impact', 'Влияние')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'technical_pagespeed_metrics',
            $this->aysa_adjustments_tr($language, 'PageSpeed metrics', 'Metrici PageSpeed', 'PageSpeed метрики'),
            $this->aysa_adjustments_tr($language, 'Desktop and mobile performance metrics.', 'Metrici de performanță pentru desktop și mobile.', 'Performance метрики за desktop и mobile.'),
            $this->aysa_adjustments_tr($language, 'Filter metrics', 'Filtrează metricile', 'Филтрирай метриките'),
            isset($modules['technical_pagespeed']['metrics_rows']) ? $modules['technical_pagespeed']['metrics_rows'] : array(),
            array(
                array('key' => 'metric', 'label' => $this->aysa_adjustments_tr($language, 'Metric', 'Metrică', 'Метрика')),
                array('key' => 'desktop', 'label' => $this->aysa_adjustments_tr($language, 'Desktop', 'Desktop', 'Desktop')),
                array('key' => 'mobile', 'label' => $this->aysa_adjustments_tr($language, 'Mobile', 'Mobile', 'Mobile')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'technical_mobile_optimization',
            $this->aysa_adjustments_tr($language, 'Mobile optimization', 'Optimizare mobile', 'Mobile оптимизация'),
            $this->aysa_adjustments_tr($language, 'Mobile-focused performance and usability summary.', 'Rezumat axat pe performanța și usability-ul pe mobile.', 'Обобщение за performance и usability на mobile.'),
            $this->aysa_adjustments_tr($language, 'Filter mobile metrics', 'Filtrează metricile mobile', 'Филтрирай mobile метриките'),
            isset($modules['technical_mobile_optimization']['overview_rows']) ? $modules['technical_mobile_optimization']['overview_rows'] : array(),
            array(
                array('key' => 'metric', 'label' => $this->aysa_adjustments_tr($language, 'Metric', 'Metrică', 'Метрика')),
                array('key' => 'value', 'label' => $this->aysa_adjustments_tr($language, 'Value', 'Valoare', 'Стойност')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'technical_mobile_optimization_opportunities',
            $this->aysa_adjustments_tr($language, 'Mobile opportunities', 'Oportunități mobile', 'Mobile възможности'),
            $this->aysa_adjustments_tr($language, 'The main mobile-only opportunities reported by PageSpeed.', 'Principalele oportunități doar pentru mobile raportate de PageSpeed.', 'Основните възможности само за mobile, отчетени от PageSpeed.'),
            $this->aysa_adjustments_tr($language, 'Filter mobile opportunities', 'Filtrează oportunitățile mobile', 'Филтрирай mobile възможностите'),
            isset($modules['technical_mobile_optimization']['rows']) ? $modules['technical_mobile_optimization']['rows'] : array(),
            array(
                array('key' => 'title', 'label' => $this->aysa_adjustments_tr($language, 'Opportunity', 'Oportunitate', 'Възможност')),
                array('key' => 'display_value', 'label' => $this->aysa_adjustments_tr($language, 'Display value', 'Valoare afișată', 'Показвана стойност')),
                array('key' => 'impact', 'label' => $this->aysa_adjustments_tr($language, 'Impact', 'Impact', 'Влияние')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'technical_errors',
            $this->aysa_adjustments_tr($language, 'Errors hub', 'Hub erori', 'Errors hub'),
            $this->aysa_adjustments_tr($language, 'Unified 404 / 301 / 410 snapshot for review, approval, and automation.', 'Snapshot unificat 404 / 301 / 410 pentru review, aprobare și automatizare.', 'Обединен 404 / 301 / 410 snapshot за преглед, одобрение и автоматизация.'),
            $this->aysa_adjustments_tr($language, 'Filter technical errors', 'Filtrează erorile tehnice', 'Филтрирай техническите грешки'),
            isset($modules['technical_errors']['rows']) ? $modules['technical_errors']['rows'] : array(),
            array(
                array('key' => 'kind', 'label' => $this->aysa_adjustments_tr($language, 'Kind', 'Tip', 'Вид')),
                array('key' => 'source_url', 'label' => $this->aysa_adjustments_tr($language, 'Source URL', 'URL sursă', 'Source URL')),
                array('key' => 'suggested_action', 'label' => $this->aysa_adjustments_tr($language, 'Suggested action', 'Acțiune sugerată', 'Предложено действие')),
                array('key' => 'suggested_target_url', 'label' => $this->aysa_adjustments_tr($language, 'Suggested target', 'Țintă sugerată', 'Предложена цел')),
                array('key' => 'suggested_confidence', 'label' => $this->aysa_adjustments_tr($language, 'Confidence', 'Încredere', 'Увереност')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'technical_404_errors',
            $this->aysa_adjustments_tr($language, '404 errors', 'Erori 404', '404 грешки'),
            $this->aysa_adjustments_tr($language, 'URLs that currently return 404 and need attention.', 'URL-uri care întorc acum 404 și au nevoie de atenție.', 'URL адреси, които в момента връщат 404 и изискват внимание.'),
            $this->aysa_adjustments_tr($language, 'Filter 404 URLs', 'Filtrează URL-urile 404', 'Филтрирай 404 URL адресите'),
            isset($modules['technical_404_errors']['rows']) ? $modules['technical_404_errors']['rows'] : array(),
            array(
                array('key' => 'url', 'label' => $this->aysa_adjustments_tr($language, '404 URL', 'URL 404', '404 URL')),
                array('key' => 'source', 'label' => $this->aysa_adjustments_tr($language, 'Source', 'Sursă', 'Източник')),
                array('key' => 'hits', 'label' => $this->aysa_adjustments_tr($language, 'Hits', 'Lovituri', 'Попадения')),
                array('key' => 'best_redirect_url', 'label' => $this->aysa_adjustments_tr($language, 'Suggested redirect', 'Redirect sugerat', 'Предложен redirect')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'technical_301_redirects',
            $this->aysa_adjustments_tr($language, '301 redirects', 'Redirecturi 301', '301 redirect-и'),
            $this->aysa_adjustments_tr($language, 'Redirect rules currently managed in the project.', 'Regulile de redirect gestionate acum în proiect.', 'Redirect правилата, които в момента се управляват в проекта.'),
            $this->aysa_adjustments_tr($language, 'Filter redirects', 'Filtrează redirecturile', 'Филтрирай redirect-ите'),
            isset($modules['technical_301_redirects']['rows']) ? $modules['technical_301_redirects']['rows'] : array(),
            array(
                array('key' => 'source_url', 'label' => $this->aysa_adjustments_tr($language, 'Source URL', 'URL sursă', 'Source URL')),
                array('key' => 'target_url', 'label' => $this->aysa_adjustments_tr($language, 'Target URL', 'URL destinație', 'Target URL')),
                array('key' => 'status', 'label' => $this->aysa_adjustments_tr($language, 'Status', 'Status', 'Статус')),
                array('key' => 'rule_type', 'label' => $this->aysa_adjustments_tr($language, 'Type', 'Tip', 'Тип')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'technical_410_gone',
            $this->aysa_adjustments_tr($language, '410 gone URLs', 'URL-uri marcate 410 Gone', '410 gone URL адреси'),
            $this->aysa_adjustments_tr($language, 'URLs intentionally marked as gone and removed from search.', 'URL-uri marcate intenționat ca gone și scoase din căutare.', 'URL адреси, умишлено маркирани като gone и извадени от търсенето.'),
            $this->aysa_adjustments_tr($language, 'Filter gone URLs', 'Filtrează URL-urile gone', 'Филтрирай gone URL адресите'),
            isset($modules['technical_410_gone']['rows']) ? $modules['technical_410_gone']['rows'] : array(),
            array(
                array('key' => 'url', 'label' => $this->aysa_adjustments_tr($language, 'Gone URL', 'URL marcat gone', 'Gone URL')),
                array('key' => 'status', 'label' => $this->aysa_adjustments_tr($language, 'Status', 'Status', 'Статус')),
                array('key' => 'rule_type', 'label' => $this->aysa_adjustments_tr($language, 'Type', 'Tip', 'Тип')),
                array('key' => 'source', 'label' => $this->aysa_adjustments_tr($language, 'Source', 'Sursă', 'Източник')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'technical_schema_markup',
            $this->aysa_adjustments_tr($language, 'Schema markup', 'Schema markup', 'Schema markup'),
            $this->aysa_adjustments_tr($language, 'Schema coverage across the pages currently checked by the project.', 'Acoperirea schema pe paginile verificate acum de proiect.', 'Schema покритието върху страниците, които в момента се проверяват от проекта.'),
            $this->aysa_adjustments_tr($language, 'Filter schema rows', 'Filtrează rândurile schema', 'Филтрирай schema редовете'),
            isset($modules['technical_schema_markup']['rows']) ? $modules['technical_schema_markup']['rows'] : array(),
            array(
                array('key' => 'title', 'label' => $this->aysa_adjustments_tr($language, 'Page', 'Pagină', 'Страница')),
                array('key' => 'page_type', 'label' => $this->aysa_adjustments_tr($language, 'Recommended schema', 'Schema recomandată', 'Препоръчана schema')),
                array('key' => 'status', 'label' => $this->aysa_adjustments_tr($language, 'Status', 'Status', 'Статус')),
                array('key' => 'url', 'label' => $this->aysa_adjustments_tr($language, 'URL', 'URL', 'URL')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'technical_internal_linking',
            $this->aysa_adjustments_tr($language, 'Internal linking', 'Linkuri interne', 'Вътрешни линкове'),
            $this->aysa_adjustments_tr($language, 'Current internal linking report grouped by keyword.', 'Raportul actual de linkuri interne, grupat pe keyword.', 'Текущият отчет за вътрешни линкове, групиран по keyword.'),
            $this->aysa_adjustments_tr($language, 'Filter internal links', 'Filtrează linkurile interne', 'Филтрирай вътрешните линкове'),
            isset($modules['technical_internal_linking']['rows']) ? $modules['technical_internal_linking']['rows'] : array(),
            array(
                array('key' => 'keyword', 'label' => $this->aysa_adjustments_tr($language, 'Keyword', 'Keyword', 'Keyword')),
                array('key' => 'target_title', 'label' => $this->aysa_adjustments_tr($language, 'Target page', 'Pagină țintă', 'Целева страница')),
                array('key' => 'total_links', 'label' => $this->aysa_adjustments_tr($language, 'Links added', 'Linkuri adăugate', 'Добавени линкове')),
                array('key' => 'sources_count', 'label' => $this->aysa_adjustments_tr($language, 'Source pages', 'Pagini sursă', 'Източници')),
                array('key' => 'last_placement', 'label' => $this->aysa_adjustments_tr($language, 'Last placement', 'Ultima plasare', 'Последно поставяне')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'technical_internal_linking_preview',
            $this->aysa_adjustments_tr($language, 'Internal linking opportunities', 'Oportunități de linkuri interne', 'Възможности за вътрешни линкове'),
            $this->aysa_adjustments_tr($language, 'Preview of the links that can be added safely next.', 'Previzualizare a linkurilor care pot fi adăugate în siguranță în pasul următor.', 'Преглед на линковете, които могат да бъдат добавени безопасно на следващата стъпка.'),
            $this->aysa_adjustments_tr($language, 'Filter opportunities', 'Filtrează oportunitățile', 'Филтрирай възможностите'),
            isset($modules['technical_internal_linking']['preview_rows']) ? $modules['technical_internal_linking']['preview_rows'] : array(),
            array(
                array('key' => 'content_title', 'label' => $this->aysa_adjustments_tr($language, 'Source content', 'Conținut sursă', 'Изходно съдържание')),
                array('key' => 'content_type', 'label' => $this->aysa_adjustments_tr($language, 'Type', 'Tip', 'Тип')),
                array('key' => 'links_count', 'label' => $this->aysa_adjustments_tr($language, 'Links to add', 'Linkuri de adăugat', 'Линкове за добавяне')),
                array('key' => 'keywords', 'label' => $this->aysa_adjustments_tr($language, 'Keywords', 'Keyword-uri', 'Keyword-и')),
                array('key' => 'targets', 'label' => $this->aysa_adjustments_tr($language, 'Targets', 'Ținte', 'Цели')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'technical_sitemap_robots',
            $this->aysa_adjustments_tr($language, 'Sitemap + Robots overview', 'Privire generală Sitemap + Robots', 'Преглед на Sitemap + Robots'),
            $this->aysa_adjustments_tr($language, 'The current sitemap and robots.txt status.', 'Starea curentă pentru sitemap și robots.txt.', 'Текущото състояние на sitemap и robots.txt.'),
            $this->aysa_adjustments_tr($language, 'Filter checks', 'Filtrează verificările', 'Филтрирай проверките'),
            isset($modules['technical_sitemap_robots']['overview_rows']) ? $modules['technical_sitemap_robots']['overview_rows'] : array(),
            array(
                array('key' => 'metric', 'label' => $this->aysa_adjustments_tr($language, 'Metric', 'Metrică', 'Метрика')),
                array('key' => 'value', 'label' => $this->aysa_adjustments_tr($language, 'Value', 'Valoare', 'Стойност')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'technical_sitemap_robots_checks',
            $this->aysa_adjustments_tr($language, 'Sitemap + Robots checks', 'Verificări Sitemap + Robots', 'Sitemap + Robots проверки'),
            $this->aysa_adjustments_tr($language, 'The checks performed on sitemap endpoints and robots.txt.', 'Verificările făcute pe endpointurile sitemap și pe robots.txt.', 'Проверките върху sitemap endpoint-ите и robots.txt.'),
            $this->aysa_adjustments_tr($language, 'Filter checks', 'Filtrează verificările', 'Филтрирай проверките'),
            isset($modules['technical_sitemap_robots']['rows']) ? $modules['technical_sitemap_robots']['rows'] : array(),
            array(
                array('key' => 'label', 'label' => $this->aysa_adjustments_tr($language, 'Check', 'Verificare', 'Проверка')),
                array('key' => 'status', 'label' => $this->aysa_adjustments_tr($language, 'Status', 'Status', 'Статус')),
                array('key' => 'message', 'label' => $this->aysa_adjustments_tr($language, 'Details', 'Detalii', 'Детайли')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'technical_sitemap_robots_sitemaps',
            $this->aysa_adjustments_tr($language, 'Detected sitemaps', 'Sitemap-uri detectate', 'Открити sitemap-и'),
            $this->aysa_adjustments_tr($language, 'Detected sitemap endpoints and their reachability.', 'Endpointurile sitemap detectate și starea lor.', 'Откритите sitemap endpoint-и и тяхната достъпност.'),
            $this->aysa_adjustments_tr($language, 'Filter sitemap endpoints', 'Filtrează sitemap-urile', 'Филтрирай sitemap-ите'),
            isset($modules['technical_sitemap_robots']['sitemaps']) ? $modules['technical_sitemap_robots']['sitemaps'] : array(),
            array(
                array('key' => 'url', 'label' => $this->aysa_adjustments_tr($language, 'URL', 'URL', 'URL')),
                array('key' => 'reachable', 'label' => $this->aysa_adjustments_tr($language, 'Reachable', 'Accesibil', 'Достъпен')),
                array('key' => 'message', 'label' => $this->aysa_adjustments_tr($language, 'Details', 'Detalii', 'Детайли')),
            )
        );
        $extra_sections[] = $this->aysa_adjustments_build_table_section(
            'technical_sitemap_robots_file',
            $this->aysa_adjustments_tr($language, 'robots.txt', 'robots.txt', 'robots.txt'),
            $this->aysa_adjustments_tr($language, 'Current robots.txt source and content.', 'Sursa și conținutul curent pentru robots.txt.', 'Текущият source и content на robots.txt.'),
            $this->aysa_adjustments_tr($language, 'Filter robots content', 'Filtrează robots', 'Филтрирай robots'),
            isset($modules['technical_sitemap_robots']['robots_rows']) ? $modules['technical_sitemap_robots']['robots_rows'] : array(),
            array(
                array('key' => 'field', 'label' => $this->aysa_adjustments_tr($language, 'Field', 'Câmp', 'Поле')),
                array('key' => 'value', 'label' => $this->aysa_adjustments_tr($language, 'Value', 'Valoare', 'Стойност')),
            )
        );

        foreach ($extra_sections as $section) {
            if (!empty($section)) {
                $artifact['sections'][] = $section;
            }
        }

        return $artifact;
    }

    private function aysa_adjustments_build_table_section($id, $title, $summary, $filter_placeholder, $rows, $columns) {
        if (!is_array($rows) || $rows === array()) {
            return array();
        }
        return array(
            'id' => sanitize_key((string) $id),
            'title' => (string) $title,
            'button_label' => (string) $title,
            'summary' => (string) $summary,
            'kind' => 'table',
            'filter_placeholder' => (string) $filter_placeholder,
            'empty_label' => 'No rows available.',
            'columns' => array_values($columns),
            'rows' => array_values($rows),
        );
    }

    private function aysa_adjustments_run_technical_site_audit_refresh($project_id, $start_if_missing = false) {
        $project_id = intval($project_id);
        $snapshot = $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id);
        $job_id = sanitize_text_field((string) ($snapshot['job_id'] ?? ''));

        if ($job_id === '' && $start_if_missing) {
            $target_url = $this->aysa_technical_resolve_target_url();
            $payload = array(
                'project_id' => $project_id,
                'url' => $target_url,
                'job_id' => 'wp_' . $project_id . '_' . gmdate('Ymd_His'),
                'mode' => 'baseline_full',
            );
            $start = $this->aysa_broker_request('POST', '/v1/technical/screamingfrog/start', $payload, 90);
            if (is_array($start) && !empty($start['ok'])) {
                update_option($this->aysa_technical_site_audit_state_key($project_id), array(
                    'project_id' => $project_id,
                    'job_id' => (string) ($start['job_id'] ?? ''),
                    'target_url' => (string) ($start['target_url'] ?? $target_url),
                    'status' => 'running',
                    'started_at' => current_time('mysql'),
                    'updated_at' => current_time('mysql'),
                    'mode' => (string) ($start['mode'] ?? 'baseline_full'),
                    'config_path' => (string) ($start['config_path'] ?? ''),
                    'export_tabs' => (string) ($start['export_tabs'] ?? ''),
                    'bulk_exports' => (string) ($start['bulk_exports'] ?? ''),
                    'reports' => (string) ($start['reports'] ?? ''),
                    'result_count' => 0,
                ), false);
                $snapshot = $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id);
                $job_id = sanitize_text_field((string) ($snapshot['job_id'] ?? ''));
            }
        }

        if ($job_id !== '') {
            $status = $this->aysa_broker_request('POST', '/v1/technical/screamingfrog/status', array('job_id' => $job_id), 45);
            if (is_array($status)) {
                $state = get_option($this->aysa_technical_site_audit_state_key($project_id), array());
                if (!is_array($state)) {
                    $state = array();
                }
                $state = array_merge($state, array(
                    'project_id' => $project_id,
                    'job_id' => $job_id,
                    'status' => (string) ($status['status'] ?? ($state['status'] ?? 'unknown')),
                    'updated_at' => current_time('mysql'),
                    'job_dir' => (string) ($status['job_dir'] ?? ''),
                    'mode' => (string) ($status['mode'] ?? ($state['mode'] ?? 'baseline_full')),
                    'config_path' => (string) ($status['config_path'] ?? ($state['config_path'] ?? '')),
                    'export_tabs' => (string) ($status['export_tabs'] ?? ($state['export_tabs'] ?? '')),
                    'bulk_exports' => (string) ($status['bulk_exports'] ?? ($state['bulk_exports'] ?? '')),
                    'reports' => (string) ($status['reports'] ?? ($state['reports'] ?? '')),
                    'artifacts' => isset($status['artifacts']) && is_array($status['artifacts']) ? $status['artifacts'] : array(),
                ));
                update_option($this->aysa_technical_site_audit_state_key($project_id), $state, false);
            }

            $results = $this->aysa_broker_request('POST', '/v1/technical/screamingfrog/results', array(
                'job_id' => $job_id,
                'preview_rows' => 50,
            ), 60);
            if (is_array($results)) {
                $state = get_option($this->aysa_technical_site_audit_state_key($project_id), array());
                if (!is_array($state)) {
                    $state = array();
                }
                $state = array_merge($state, array(
                    'project_id' => $project_id,
                    'job_id' => $job_id,
                    'status' => (string) ($results['status'] ?? ($state['status'] ?? 'unknown')),
                    'updated_at' => current_time('mysql'),
                    'mode' => (string) ($results['mode'] ?? ($state['mode'] ?? 'baseline_full')),
                    'config_path' => (string) ($results['config_path'] ?? ($state['config_path'] ?? '')),
                    'export_tabs' => (string) ($results['export_tabs'] ?? ($state['export_tabs'] ?? '')),
                    'bulk_exports' => (string) ($results['bulk_exports'] ?? ($state['bulk_exports'] ?? '')),
                    'reports' => (string) ($results['reports'] ?? ($state['reports'] ?? '')),
                    'result_count' => count(isset($results['files']) && is_array($results['files']) ? $results['files'] : array()),
                    'latest_output_dir' => (string) ($results['latest_output_dir'] ?? ''),
                    'audit_snapshot' => isset($results['audit']) && is_array($results['audit']) ? $results['audit'] : array(),
                    'result_files' => isset($results['files']) && is_array($results['files']) ? $results['files'] : array(),
                ));
                $version_id = $this->aysa_sf_site_audit_persist_version($project_id, $state);
                if ($version_id > 0) {
                    $latest_version = $this->aysa_sf_site_audit_get_latest_version_row($project_id);
                    if (is_array($latest_version) && !empty($latest_version)) {
                        $state['version_id'] = intval($latest_version['id'] ?? 0);
                        $state['version_number'] = intval($latest_version['version_number'] ?? 0);
                    }
                }
                update_option($this->aysa_technical_site_audit_state_key($project_id), $state, false);
            }
        }

        return $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id);
    }

    private function aysa_adjustments_build_technical_site_audit_completion_message($snapshot, $language = 'en', $just_started = false) {
        $snapshot = is_array($snapshot) ? $snapshot : array();
        $status = sanitize_key((string) ($snapshot['status'] ?? 'idle'));
        $pages_crawled = intval($snapshot['pages_crawled'] ?? 0);
        $site_health = intval($snapshot['site_health'] ?? 0);
        $errors = intval($snapshot['errors'] ?? 0);
        $warnings = intval($snapshot['warnings'] ?? 0);
        $rows = isset($snapshot['rows']) && is_array($snapshot['rows']) ? array_values($snapshot['rows']) : array();
        $issue_titles = array();
        foreach (array_slice($rows, 0, 3) as $row) {
            if (!is_array($row)) {
                continue;
            }
            $title = trim((string) ($row['title'] ?? ''));
            if ($title !== '') {
                $issue_titles[] = $title;
            }
        }
        $issues_preview = implode(', ', $issue_titles);

        if (($status === 'running' || $status === 'queued') && $pages_crawled <= 0) {
            return $this->aysa_adjustments_tr(
                $language,
                'I started the full technical audit. The crawl is running now, and I will use the saved results as soon as the first pages and issues are available.',
                'Am pornit auditul tehnic complet. Crawl-ul rulează acum, iar imediat ce apar primele pagini și probleme, folosesc rezultatele salvate aici.',
                'Стартирах пълния технически одит. Обхождането върви сега и веднага щом има първи страници и проблеми, ще използвам запазените резултати тук.'
            );
        }

        if ($pages_crawled > 0 || !empty($rows)) {
            return match ($language) {
                'ro' => 'Am audit tehnic salvat pentru site. În acest moment au fost crawl-ate ' . $pages_crawled . ' pagini, health score-ul este ' . $site_health . '%, am ' . $errors . ' erori și ' . $warnings . ' avertismente. Cele mai vizibile probleme sunt: ' . ($issues_preview !== '' ? $issues_preview : 'câteva probleme tehnice care merită verificate') . '. Dacă vrei, îți arăt imediat problemele sau paginile crawl-ate.',
                'bg' => 'Имам запазен технически одит за сайта. В момента са обходени ' . $pages_crawled . ' страници, health score е ' . $site_health . '%, има ' . $errors . ' грешки и ' . $warnings . ' предупреждения. Най-видимите проблеми са: ' . ($issues_preview !== '' ? $issues_preview : 'няколко технически проблема, които си струва да се проверят') . '. Ако искаш, веднага ще ти покажа проблемите или обходените страници.',
                default => 'I already have a saved technical audit for the site. Right now it has crawled ' . $pages_crawled . ' pages, the health score is ' . $site_health . '%, and I can see ' . $errors . ' errors and ' . $warnings . ' warnings. The most visible issues are: ' . ($issues_preview !== '' ? $issues_preview : 'a few technical issues worth reviewing') . '. If you want, I can show you the issues or the crawled pages next.',
            };
        }

        return $this->aysa_adjustments_tr(
            $language,
            'I do not have a saved technical audit yet. I can start a full site audit now and then show you the errors, warnings, and crawled pages.',
            'Încă nu am un audit tehnic salvat. Pot porni acum un audit complet de site și apoi îți arăt erorile, avertismentele și paginile crawl-ate.',
            'Все още нямам запазен технически одит. Мога да стартирам пълен site audit сега и после да ти покажа грешките, предупрежденията и обходените страници.'
        );
    }

    private function aysa_adjustments_run_technical_pagespeed_refresh($project_id, $refresh = true) {
        $project_id = intval($project_id);
        $target_url = $this->aysa_technical_resolve_target_url();
        $payload = array(
            'project_id' => $project_id,
            'url' => $target_url,
            'force_refresh' => !empty($refresh),
        );
        $result = $this->aysa_broker_request('POST', '/v1/technical/pagespeed/summary', $payload, 90);
        if (is_wp_error($result)) {
            $fallback = $this->aysa_pagespeed_summary_fallback($target_url);
            if (!is_wp_error($fallback)) {
                $result = $fallback;
            } else {
                $legacy = $this->aysa_pagespeed_summary_from_legacy_endpoints($project_id, $target_url, !empty($refresh));
                if (!is_wp_error($legacy)) {
                    $result = $legacy;
                } else {
                    $state = get_option($this->aysa_technical_pagespeed_state_key($project_id), array());
                    if (is_array($state) && !empty($state['data']) && is_array($state['data'])) {
                        $result = $state['data'];
                    } else {
                        $result = array();
                    }
                }
            }
        }

        if (is_array($result) && !empty($result)) {
            update_option($this->aysa_technical_pagespeed_state_key($project_id), array(
                'project_id' => $project_id,
                'updated_at' => current_time('mysql'),
                'updated_at_unix' => time(),
                'data' => $result,
            ), false);
        }

        return $this->aysa_adjustments_get_technical_pagespeed_snapshot($project_id);
    }

    private function aysa_adjustments_run_technical_sitemap_robots_refresh($project_id, $refresh = true) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array();
        }

        $target_url = $this->aysa_technical_resolve_target_url();
        $state_key = $this->aysa_technical_sitemap_robots_state_key($project_id);
        $cached = get_option($state_key, array());
        if (!is_array($cached)) {
            $cached = array();
        }

        $max_age_seconds = 6 * HOUR_IN_SECONDS;
        if (
            !$refresh &&
            !empty($cached['updated_at_unix']) &&
            (time() - intval($cached['updated_at_unix']) < $max_age_seconds) &&
            !empty($cached['data']) &&
            is_array($cached['data'])
        ) {
            return $this->aysa_adjustments_get_technical_sitemap_robots_snapshot($project_id);
        }

        $audit = $this->aysa_technical_build_sitemap_robots_audit($target_url);
        $state = array(
            'project_id' => $project_id,
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'data' => $audit,
        );
        update_option($state_key, $state, false);
        return $this->aysa_adjustments_get_technical_sitemap_robots_snapshot($project_id);
    }

    private function aysa_adjustments_build_technical_sitemap_robots_completion_message($snapshot, $language = 'en') {
        $snapshot = is_array($snapshot) ? $snapshot : array();
        $errors = intval($snapshot['errors'] ?? 0);
        $warnings = intval($snapshot['warnings'] ?? 0);
        $passes = intval($snapshot['passes'] ?? 0);
        $robots_health = sanitize_text_field((string) ($snapshot['robots_health'] ?? ''));
        $sitemap_health = sanitize_text_field((string) ($snapshot['sitemap_health'] ?? ''));
        $checks = isset($snapshot['rows']) && is_array($snapshot['rows']) ? $snapshot['rows'] : array();
        $sitemaps = isset($snapshot['sitemaps']) && is_array($snapshot['sitemaps']) ? $snapshot['sitemaps'] : array();
        $primary = trim((string) ($snapshot['primary_sitemap'] ?? ''));

        $topIssues = array();
        foreach (array_slice(array_values(array_filter($checks, static function ($row) {
            return is_array($row) && ((string) ($row['status'] ?? '')) !== 'pass';
        })), 0, 3) as $row) {
            $label = trim((string) ($row['label'] ?? ''));
            if ($label !== '') {
                $topIssues[] = $label;
            }
        }
        $issuePreview = implode(', ', $topIssues);
        $reachable = 0;
        foreach ($sitemaps as $row) {
            if (!empty($row['reachable'])) {
                $reachable++;
            }
        }

        return match ($language) {
            'ro' => 'Am verificat sitemap-ul și robots.txt. În acest moment am ' . $errors . ' probleme, ' . $warnings . ' avertismente și ' . $passes . ' verificări care trec corect. Sănătatea robots.txt este ' . ($robots_health !== '' ? $robots_health : 'necunoscută') . ', iar sănătatea sitemap-ului este ' . ($sitemap_health !== '' ? $sitemap_health : 'necunoscută') . '. '
                . ($primary !== '' ? 'Sitemap-ul principal detectat este ' . $primary . '. ' : '')
                . ($reachable > 0 ? 'Am găsit ' . $reachable . ' endpointuri sitemap accesibile. ' : 'Nu am găsit încă un endpoint sitemap accesibil. ')
                . ($issuePreview !== '' ? 'Cele mai importante puncte de verificat sunt ' . $issuePreview . '. ' : '')
                . 'Dacă vrei, îți arăt verificările, sitemap-urile detectate sau conținutul actual din robots.txt.',
            'bg' => 'Проверих sitemap-а и robots.txt. В момента имам ' . $errors . ' проблема, ' . $warnings . ' предупреждения и ' . $passes . ' проверки, които минават успешно. Health на robots.txt е ' . ($robots_health !== '' ? $robots_health : 'неизвестен') . ', а health на sitemap-а е ' . ($sitemap_health !== '' ? $sitemap_health : 'неизвестен') . '. '
                . ($primary !== '' ? 'Откритият primary sitemap е ' . $primary . '. ' : '')
                . ($reachable > 0 ? 'Намерих ' . $reachable . ' достъпни sitemap endpoint-а. ' : 'Все още не намерих достъпен sitemap endpoint. ')
                . ($issuePreview !== '' ? 'Най-важните неща за проверка са ' . $issuePreview . '. ' : '')
                . 'Ако искаш, ще ти покажа проверките, откритите sitemap-и или текущото съдържание на robots.txt.',
            default => 'I checked the sitemap and robots.txt. Right now I have ' . $errors . ' issues, ' . $warnings . ' warnings, and ' . $passes . ' checks passing correctly. robots.txt health is ' . ($robots_health !== '' ? $robots_health : 'unknown') . ', and sitemap health is ' . ($sitemap_health !== '' ? $sitemap_health : 'unknown') . '. '
                . ($primary !== '' ? 'The primary sitemap detected is ' . $primary . '. ' : '')
                . ($reachable > 0 ? 'I found ' . $reachable . ' reachable sitemap endpoints. ' : 'I have not found a reachable sitemap endpoint yet. ')
                . ($issuePreview !== '' ? 'The main things worth checking are ' . $issuePreview . '. ' : '')
                . 'If you want, I can show you the checks, the detected sitemaps, or the current robots.txt content.',
        };
    }

    private function aysa_adjustments_run_technical_internal_linking_preview($project_id) {
        $service = $this->aysa_internal_linking_workspace_service();
        if ($service) {
            $result = $service->run_legacy_action(intval($project_id), 'analyze');
            if (!empty($result['ok']) && is_array($result['result'] ?? null)) {
                return array(
                    'total_keywords_found' => intval($result['result']['total_keywords_found'] ?? 0),
                    'content_items_scanned' => intval($result['result']['content_items_scanned'] ?? 0),
                    'links_to_be_added' => intval($result['result']['links_added'] ?? 0),
                    'preview_changes' => $this->aysa_adjustments_format_internal_linking_preview_changes($result['result']['link_mappings'] ?? array()),
                    'summary' => sanitize_text_field((string) ($result['result']['summary'] ?? '')),
                    'success' => !empty($result['result']['success']),
                    'message' => sanitize_text_field((string) ($result['result']['message'] ?? '')),
                );
            }
        }

        if (!class_exists('Aysa_Internal_Linking')) {
            return array('success' => false, 'message' => 'Internal linking class is not available.');
        }
        $linking = new Aysa_Internal_Linking();
        $result = $linking->process_all_content_with_main_keywords(true, true);
        $state = array(
            'project_id' => $project_id,
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'data' => array(
                'total_keywords_found' => intval($result['total_keywords_found'] ?? 0),
                'content_items_scanned' => intval($result['content_items_scanned'] ?? 0),
                'links_to_be_added' => intval($result['links_added'] ?? 0),
                'preview_changes' => $this->aysa_adjustments_format_internal_linking_preview_changes($result['link_mappings'] ?? array()),
                'summary' => sanitize_text_field((string) ($result['summary'] ?? '')),
                'success' => !empty($result['success']),
                'message' => sanitize_text_field((string) ($result['message'] ?? '')),
            ),
        );
        update_option($this->aysa_technical_internal_linking_preview_state_key($project_id), $state, false);
        return $state['data'];
    }

    private function aysa_adjustments_run_technical_internal_linking_process($project_id) {
        $service = $this->aysa_internal_linking_workspace_service();
        if ($service) {
            $result = $service->run_legacy_action(intval($project_id), 'apply_safe');
            if (!empty($result['ok']) && is_array($result['result'] ?? null)) {
                return $result['result'];
            }
        }

        if (!class_exists('Aysa_Internal_Linking')) {
            return array('success' => false, 'message' => 'Internal linking class is not available.');
        }
        $linking = new Aysa_Internal_Linking();
        $result = $linking->process_all_content_with_main_keywords(false);
        delete_option($this->aysa_technical_internal_linking_preview_state_key($project_id));
        return is_array($result) ? $result : array('success' => false, 'message' => 'Internal linking process did not return a valid result.');
    }

    private function aysa_adjustments_run_onpage_optimize_needed($project_id, $limit = 5) {
        $project_id = intval($project_id);
        $limit = max(1, min(10, intval($limit)));
        $snapshot = $this->aysa_adjustments_get_onpage_optimization_snapshot($project_id);
        $rows = isset($snapshot['rows']) && is_array($snapshot['rows']) ? $snapshot['rows'] : array();

        if (!class_exists('Aysa_Seo_Generation_Processor')) {
            $processor_file = plugin_dir_path(dirname(__FILE__)) . 'includes/automation/processors/class-seo-generation-processor.php';
            if (file_exists($processor_file)) {
                require_once $processor_file;
            }
        }

        if (!class_exists('Aysa_Seo_Generation_Processor')) {
            return array(
                'processed' => 0,
                'generated' => 0,
                'existing' => 0,
                'errors' => 1,
                'items' => array(),
                'error_message' => 'SEO generation processor is not available.',
            );
        }

        $processor = new Aysa_Seo_Generation_Processor();
        $items = array();
        $generated = 0;
        $existing = 0;
        $errors = 0;

        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }

            $status = (string) ($row['optimized'] ?? '');
            if ($status === 'Optimized') {
                continue;
            }

            $entity_id = intval($row['entity_id'] ?? 0);
            $entity_type = trim((string) ($row['entity_processor_type'] ?? ''));
            $keyword = trim((string) ($row['main_keyword'] ?? ''));
            if ($entity_id <= 0 || $entity_type === '' || $keyword === '') {
                continue;
            }

            if ($this->aysa_adjustments_has_pending_seo_review($entity_id, $entity_type, $keyword)) {
                $existing++;
                $items[] = array(
                    'entity' => (string) ($row['entity'] ?? ''),
                    'keyword' => $keyword,
                    'status' => 'already_pending',
                );
                if (count($items) >= $limit) {
                    break;
                }
                continue;
            }

            $result = $processor->process(array(
                'entity_id' => $entity_id,
                'entity_type' => $entity_type,
                'keyword' => $keyword,
                'project_id' => $project_id,
            ));

            if (is_wp_error($result)) {
                $errors++;
                $items[] = array(
                    'entity' => (string) ($row['entity'] ?? ''),
                    'keyword' => $keyword,
                    'status' => 'error',
                );
            } else {
                $generated++;
                $items[] = array(
                    'entity' => (string) ($row['entity'] ?? ''),
                    'keyword' => $keyword,
                    'status' => 'generated',
                );
            }

            if (count($items) >= $limit) {
                break;
            }
        }

        return array(
            'processed' => count($items),
            'generated' => $generated,
            'existing' => $existing,
            'errors' => $errors,
            'items' => $items,
        );
    }

    private function aysa_adjustments_has_pending_seo_review($entity_id, $entity_type, $keyword) {
        global $wpdb;

        $entity_id = intval($entity_id);
        $entity_type = trim((string) $entity_type);
        $keyword = trim((string) $keyword);
        if ($entity_id <= 0 || $entity_type === '' || $keyword === '') {
            return false;
        }

        $table_name = esc_sql($wpdb->prefix . 'aysa_automated_pending_reviews');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $exists = $wpdb->get_var($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT COUNT(*) FROM {$table_name} WHERE entity_id = %d AND entity_type = %s AND keyword = %s AND status = 'pending'",
            $entity_id,
            $entity_type,
            $keyword
        ));

        return intval($exists) > 0;
    }

    private function aysa_adjustments_build_onpage_optimize_needed_message($result, $language = 'en') {
        $processed = intval($result['processed'] ?? 0);
        $generated = intval($result['generated'] ?? 0);
        $existing = intval($result['existing'] ?? 0);
        $errors = intval($result['errors'] ?? 0);
        $items = isset($result['items']) && is_array($result['items']) ? $result['items'] : array();
        $error_message = trim((string) ($result['error_message'] ?? ''));

        if ($error_message !== '') {
            return $this->aysa_adjustments_tr(
                $language,
                'I could not start the on-page optimization run right now. ' . $error_message,
                'Nu am putut porni acum runda de optimizare on-page. ' . $error_message,
                'Не успях да стартирам сега on-page оптимизацията. ' . $error_message
            );
        }

        $lines = array();
        foreach (array_slice($items, 0, 5) as $item) {
            if (!is_array($item)) {
                continue;
            }
            $entity = trim((string) ($item['entity'] ?? ''));
            $keyword = trim((string) ($item['keyword'] ?? ''));
            $status = trim((string) ($item['status'] ?? ''));
            if ($entity === '' || $keyword === '') {
                continue;
            }
            $suffix = match ($status) {
                'generated' => $this->aysa_adjustments_tr($language, 'added to pending review', 'trimisă în pending review', 'добавено в pending review'),
                'already_pending' => $this->aysa_adjustments_tr($language, 'already pending review', 'deja în pending review', 'вече е в pending review'),
                default => $this->aysa_adjustments_tr($language, 'could not be generated', 'nu a putut fi generată', 'не можа да бъде генерирана'),
            };
            $lines[] = '- ' . $entity . ' -> ' . $keyword . ' (' . $suffix . ')';
        }

        $summary = $this->aysa_adjustments_tr(
            $language,
            'I processed ' . $processed . ' on-page pages. I generated ' . $generated . ' new SEO reviews, ' . $existing . ' were already pending, and ' . $errors . ' failed.',
            'Am procesat ' . $processed . ' pagini on-page. Am generat ' . $generated . ' review-uri SEO noi, ' . $existing . ' erau deja în pending, iar ' . $errors . ' au eșuat.',
            'Обработих ' . $processed . ' on-page страници. Генерирах ' . $generated . ' нови SEO review-та, ' . $existing . ' вече бяха pending, а ' . $errors . ' се провалиха.'
        );

        if (!empty($lines)) {
            $summary .= "\n\n" . $this->aysa_adjustments_tr(
                $language,
                'Here are the first pages I handled:',
                'Iată primele pagini pe care le-am tratat:',
                'Ето първите страници, които обработих:'
            ) . "\n" . implode("\n", $lines);
        }

        return trim($summary);
    }

    private function aysa_adjustments_format_internal_linking_preview_changes($link_mappings) {
        $changes_by_content = array();
        foreach (is_array($link_mappings) ? $link_mappings : array() as $mapping) {
            if (!is_array($mapping)) {
                continue;
            }
            $content_id = intval($mapping['source_content_id'] ?? 0);
            $content_title = sanitize_text_field((string) ($mapping['source_content_title'] ?? ($content_id > 0 ? 'Content #' . $content_id : '')));
            if ($content_title === '') {
                continue;
            }
            if (!isset($changes_by_content[$content_title])) {
                $changes_by_content[$content_title] = array(
                    'content_title' => $content_title,
                    'content_id' => $content_id,
                    'content_type' => sanitize_text_field((string) ($mapping['source_content_type'] ?? 'post')),
                    'source_url' => esc_url_raw((string) ($mapping['source_content_url'] ?? '')),
                    'links_count' => 0,
                    'changes' => array(),
                );
            }
            $changes_by_content[$content_title]['links_count']++;
            $changes_by_content[$content_title]['changes'][] = array(
                'keyword' => sanitize_text_field((string) ($mapping['keyword'] ?? '')),
                'target_title' => sanitize_text_field((string) ($mapping['target_page'] ?? '')),
                'target_url' => esc_url_raw((string) ($mapping['target_url'] ?? '')),
                'target_post_id' => !empty($mapping['target_url']) ? intval(url_to_postid((string) $mapping['target_url'])) : 0,
                'target_type' => sanitize_text_field((string) ($mapping['target_type'] ?? '')),
                'source_content_id' => intval($mapping['source_content_id'] ?? 0),
                'source_content_type' => sanitize_text_field((string) ($mapping['source_content_type'] ?? '')),
                'source_content_url' => esc_url_raw((string) ($mapping['source_content_url'] ?? '')),
            );
        }
        return $this->aysa_adjustments_internal_linking_hydrate_grouped_preview_changes(array_values($changes_by_content), 80);
    }

    private function aysa_adjustments_build_technical_internal_linking_preview_message($result, $language = 'en') {
        if (empty($result['success'])) {
            return $this->aysa_adjustments_tr(
                $language,
                'I could not analyze internal linking opportunities right now. We can try again in a moment.',
                'Nu am putut analiza acum oportunitățile de linkuri interne. Putem încerca din nou imediat.',
                'Не успях да анализирам възможностите за вътрешни линкове точно сега. Можем да опитаме отново след малко.'
            );
        }

        return $this->aysa_adjustments_tr(
            $language,
            'I analyzed the current internal linking opportunities. I found ' . intval($result['total_keywords_found'] ?? 0) . ' relevant keywords across ' . intval($result['content_items_scanned'] ?? 0) . ' content items and I can safely add about ' . intval($result['links_to_be_added'] ?? 0) . ' internal links next.',
            'Am analizat oportunitățile actuale de linkuri interne. Am găsit ' . intval($result['total_keywords_found'] ?? 0) . ' keyword-uri relevante în ' . intval($result['content_items_scanned'] ?? 0) . ' elemente de conținut și pot adăuga în siguranță aproximativ ' . intval($result['links_to_be_added'] ?? 0) . ' linkuri interne în pasul următor.',
            'Анализирах текущите възможности за вътрешни линкове. Намерих ' . intval($result['total_keywords_found'] ?? 0) . ' релевантни keyword-а в ' . intval($result['content_items_scanned'] ?? 0) . ' елемента съдържание и мога безопасно да добавя около ' . intval($result['links_to_be_added'] ?? 0) . ' вътрешни линка в следващата стъпка.'
        );
    }

    private function aysa_adjustments_build_technical_internal_linking_process_message($result, $language = 'en') {
        if (empty($result['success'])) {
            return $this->aysa_adjustments_tr(
                $language,
                'I could not apply the internal links right now. We can try again in a moment.',
                'Nu am putut aplica acum linkurile interne. Putem încerca din nou imediat.',
                'Не успях да приложа вътрешните линкове точно сега. Можем да опитаме отново след малко.'
            );
        }

        return $this->aysa_adjustments_tr(
            $language,
            'I applied the internal linking pass. I added ' . intval($result['links_added'] ?? 0) . ' links across ' . intval($result['processed_items'] ?? 0) . ' content items, using ' . intval($result['total_keywords_found'] ?? 0) . ' matched keywords.',
            'Am aplicat runda de internal linking. Am adăugat ' . intval($result['links_added'] ?? 0) . ' linkuri în ' . intval($result['processed_items'] ?? 0) . ' elemente de conținut, folosind ' . intval($result['total_keywords_found'] ?? 0) . ' keyword-uri potrivite.',
            'Приложих internal linking pass-а. Добавих ' . intval($result['links_added'] ?? 0) . ' линка в ' . intval($result['processed_items'] ?? 0) . ' елемента съдържание, използвайки ' . intval($result['total_keywords_found'] ?? 0) . ' съвпаднали keyword-а.'
        );
    }

    private function aysa_adjustments_build_technical_pagespeed_completion_message($snapshot, $language = 'en') {
        $snapshot = is_array($snapshot) ? $snapshot : array();
        $overall = intval($snapshot['overall_score'] ?? 0);
        $desktop = intval($snapshot['desktop_score'] ?? 0);
        $mobile = intval($snapshot['mobile_score'] ?? 0);
        $rows = isset($snapshot['rows']) && is_array($snapshot['rows']) ? array_values($snapshot['rows']) : array();
        $priority = isset($snapshot['priority_actions']) && is_array($snapshot['priority_actions']) ? array_values($snapshot['priority_actions']) : array();
        $top_titles = array();
        foreach (array_slice($rows, 0, 3) as $row) {
            if (!is_array($row)) {
                continue;
            }
            $title = trim((string) ($row['title'] ?? ''));
            if ($title !== '') {
                $top_titles[] = $title;
            }
        }
        $preview = implode(', ', $top_titles);
        $priority_preview = '';
        foreach (array_slice($priority, 0, 2) as $row) {
            if (!is_array($row)) {
                continue;
            }
            $action = trim((string) ($row['action'] ?? ''));
            if ($action !== '') {
                $priority_preview .= ($priority_preview !== '' ? ', ' : '') . $action;
            }
        }

        if ($overall > 0 || !empty($rows)) {
            return match ($language) {
                'ro' => 'Am date PageSpeed salvate pentru site. În acest moment scorul general este ' . $overall . ', cu ' . $desktop . ' pe desktop și ' . $mobile . ' pe mobile. Cele mai vizibile oportunități sunt ' . ($preview !== '' ? $preview : 'câteva optimizări de performanță care merită verificate') . ($priority_preview !== '' ? '. Ca prioritate aș începe cu ' . $priority_preview : '') . '. Dacă vrei, îți arăt rezumatul complet sau oportunitățile raportate de Google.',
                'bg' => 'Имам запазени PageSpeed данни за сайта. В момента общият резултат е ' . $overall . ', с ' . $desktop . ' за desktop и ' . $mobile . ' за mobile. Най-видимите възможности са ' . ($preview !== '' ? $preview : 'няколко performance оптимизации, които си струва да се проверят') . ($priority_preview !== '' ? '. Като приоритет бих започнал с ' . $priority_preview : '') . '. Ако искаш, ще ти покажа пълното резюме или възможностите, отчетени от Google.',
                default => 'I already have saved PageSpeed data for the site. Right now the overall score is ' . $overall . ', with ' . $desktop . ' on desktop and ' . $mobile . ' on mobile. The most visible opportunities are ' . ($preview !== '' ? $preview : 'a few performance optimizations worth reviewing') . ($priority_preview !== '' ? '. As a priority I would start with ' . $priority_preview : '') . '. If you want, I can show you the full summary or the opportunities reported by Google.',
            };
        }

        return $this->aysa_adjustments_tr(
            $language,
            'I do not have a saved Google PageSpeed summary yet. I can refresh it now and then show you the desktop score, the mobile score, and the biggest performance opportunities.',
            'Încă nu am un rezumat Google PageSpeed salvat. Pot să-l reîmprospătez acum și apoi îți arăt scorul de desktop, scorul de mobile și cele mai mari oportunități de performanță.',
            'Все още нямам запазено Google PageSpeed резюме. Мога да го опресня сега и после да ти покажа desktop резултата, mobile резултата и най-големите performance възможности.'
        );
    }

    private function aysa_pagespeed_report_start_queued($broker_payload) {
        $project_id = intval($broker_payload['project_id'] ?? 0);
        if ($project_id <= 0) {
            return new WP_Error('invalid_pagespeed_project', 'Invalid project id.');
        }

        $result = $this->aysa_broker_request('POST', '/v1/technical/pagespeed/report/start', $broker_payload, 30);
        if (is_wp_error($result)) {
            return $result;
        }
        if (!is_array($result) || empty($result['ok'])) {
            return new WP_Error('pagespeed_report_start_failed', isset($result['error']) ? (string) $result['error'] : 'PageSpeed report generation failed.');
        }

        $job_id = sanitize_text_field((string) ($result['job_id'] ?? ''));
        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_technical_pagespeed_report_start',
            'technical_pagespeed_report_start:' . $job_id,
            array(
                'module' => 'technical_optimization',
                'site_url' => (string) ($broker_payload['site_url'] ?? ''),
                'site_host' => (string) ($broker_payload['site_host'] ?? ''),
                'job_id' => $job_id,
                'language' => (string) ($broker_payload['language'] ?? ''),
            ),
            36,
            '/v1/technical/pagespeed/report/start'
        );

        return array(
            'status' => sanitize_key((string) ($result['status'] ?? 'queued')),
            'worker_ref' => $this->aysa_project_jobs_make_worker_ref('pagespeed_report', 'pagespeed_report', $job_id),
            'worker_status' => sanitize_key((string) ($result['status'] ?? 'queued')),
            'result_summary' => array(
                'progress_label' => sanitize_text_field((string) ($result['progress_label'] ?? 'Queued')),
            ),
            'started_at' => time(),
        );
    }

    private function aysa_adjustments_build_keyword_research_completion_message($snapshot, $language = 'en') {
        if (!is_array($snapshot)) {
            return $this->aysa_adjustments_tr(
                $language,
                'I finished the first keyword research pass. You can open the keyword map to review the current result.',
                'Am terminat primul pas de keyword research. Poți deschide keyword map ca să vezi rezultatul curent.',
                'Приключих първия етап от keyword research. Можеш да отвориш keyword map, за да видиш текущия резултат.'
            );
        }

        $gsc_count = intval($snapshot['gsc_count'] ?? 0);
        $suggestions_count = intval($snapshot['suggestions_count'] ?? 0);
        $gsc_rows = isset($snapshot['gsc_rows']) && is_array($snapshot['gsc_rows']) ? $snapshot['gsc_rows'] : array();
        $mapped_pages = array();
        foreach ($gsc_rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $target_url = trim((string) ($row['target_url'] ?? ''));
            if ($target_url === '') {
                $target_url = trim((string) ($row['url'] ?? ''));
            }
            if ($target_url !== '') {
                $mapped_pages[$target_url] = true;
            }
        }
        $mapped_pages_count = count($mapped_pages);

        return match ($language) {
            'ro' => 'Am terminat baza pentru primul pas. Acum am ' . $gsc_count . ' keyword-uri care apar deja în Google, ' . $suggestions_count . ' sugestii noi și ' . $mapped_pages_count . ' pagini cu asocieri reale. Încep imediat să te ghidez pas cu pas. Dacă vrei să mergem mai departe, apasă `Continuă`.',
            'bg' => 'Приключих основата за първата стъпка. В момента имам ' . $gsc_count . ' keyword-а, които вече се появяват в Google, ' . $suggestions_count . ' нови предложения и ' . $mapped_pages_count . ' страници с реални свързвания. Следва да те преведа стъпка по стъпка. Ако искаш да продължим, натисни `Продължи`.',
            default => 'I finished the base for the first step. Right now I have ' . $gsc_count . ' keywords already appearing in Google, ' . $suggestions_count . ' new suggestions, and ' . $mapped_pages_count . ' pages with real matches. I can now walk you through it step by step. If you want to move on, press `Continue`.',
        };
    }

    private function aysa_adjustments_build_competitor_gap_completion_message($snapshot, $language = 'en') {
        $count = intval($snapshot['count'] ?? 0);
        if ($count <= 0) {
            return $this->aysa_adjustments_tr(
                $language,
                'I refreshed the competitor gap view, but I do not have saved gap keywords yet.',
                'Am reîmprospătat competitor gap, dar încă nu am keyword-uri salvate în gap.',
                'Опресних competitor gap изгледа, но все още нямам запазени gap keyword-и.'
            );
        }

        return match ($language) {
            'ro' => 'Am actualizat competitor gap și am identificat ' . $count . ' keyword-uri pe care competitorii le acoperă iar tu merită să le evaluezi. Din acest punct pot să te ajut să alegem ce este relevant pentru business și ce merită adăugat în keyword map.',
            'bg' => 'Актуализирах competitor gap и идентифицирах ' . $count . ' keyword-а, които конкурентите покриват, а при теб си струва да бъдат прегледани. Оттук мога да ти помогна да изберем кое е релевантно за бизнеса и какво си струва да влезе в keyword map-а.',
            default => 'I refreshed the competitor gap and identified ' . $count . ' keywords competitors already cover and you should review. From here I can help you decide what is relevant for the business and what is worth adding into the keyword map.',
        };
    }

    private function aysa_adjustments_build_competitor_identification_completion_message($snapshot, $language = 'en') {
        $rows = isset($snapshot['rows']) && is_array($snapshot['rows']) ? $snapshot['rows'] : array();
        $count = intval($snapshot['count'] ?? count($rows));
        if ($count <= 0) {
            return $this->aysa_adjustments_tr(
                $language,
                'I ran competitor discovery, but I still do not have saved competitors for the current seed keywords.',
                'Am rulat competitor discovery, dar încă nu am competitori salvați pentru seed keywords-urile curente.',
                'Стартирах competitor discovery, но все още нямам запазени конкуренти за текущите seed keywords.'
            );
        }

        return match ($language) {
            'ro' => 'Am actualizat competitor discovery și am identificat ' . $count . ' competitori relevanți pe baza temelor salvate în setup. De aici putem să comparăm keyword gap-ul și apoi să vedem unde au ei semnale mai puternice decât tine.',
            'bg' => 'Актуализирах competitor discovery и идентифицирах ' . $count . ' релевантни конкурента на база темите, запазени в setup. Оттук можем да сравним keyword gap-а и после да видим къде те имат по-силни сигнали от теб.',
            default => 'I refreshed competitor discovery and identified ' . $count . ' relevant competitors from the topics saved in setup. From here we can compare the keyword gap and then see where they have stronger signals than you.',
        };
    }

    private function aysa_adjustments_build_competitor_backlinks_completion_message($snapshot, $language = 'en') {
        $rows = isset($snapshot['rows']) && is_array($snapshot['rows']) ? $snapshot['rows'] : array();
        $count = intval($snapshot['count'] ?? count($rows));
        if ($count <= 0) {
            return $this->aysa_adjustments_tr(
                $language,
                'I refreshed the competitor backlink analysis, but I still do not have saved backlink rows to show yet.',
                'Am actualizat competitor backlink analysis, dar încă nu am rânduri salvate de backlink-uri pe care să ți le arăt.',
                'Опресних competitor backlink analysis, но все още нямам запазени backlink редове, които да ти покажа.'
            );
        }

        $stronger_count = 0;
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            if (floatval($row['authority'] ?? 0) > 0 || intval($row['backlinks'] ?? 0) > 0) {
                $stronger_count++;
            }
        }

        return match ($language) {
            'ro' => 'Am actualizat competitor backlink analysis și am găsit date salvate pentru ' . $count . ' domenii concurente. În lista de acum poți vedea rapid autoritatea și backlink-urile lor, ca să înțelegi unde au semnale mai puternice și unde merită să cauți oportunități noi.',
            'bg' => 'Актуализирах competitor backlink analysis и намерих запазени данни за ' . $count . ' конкурентни домейна. В текущия списък можеш бързо да видиш authority и backlinks, за да разбереш къде те имат по-силни сигнали и къде си струва да търсиш нови възможности.',
            default => 'I refreshed the competitor backlink analysis and found saved data for ' . $count . ' competitor domains. In the current list you can quickly review their authority and backlinks to understand where they have stronger signals and where new opportunities may be worth pursuing.',
        };
    }

    private function aysa_adjustments_build_offpage_backlink_audit_completion_message($snapshot, $language = 'en') {
        $count = intval($snapshot['count'] ?? 0);
        $domains = intval($snapshot['referring_domains'] ?? 0);
        $broken = intval($snapshot['broken_count'] ?? 0);
        $high = intval($snapshot['risk_high'] ?? 0);
        $medium = intval($snapshot['risk_medium'] ?? 0);
        $rows = isset($snapshot['rows']) && is_array($snapshot['rows']) ? $snapshot['rows'] : array();
        $examples = array();
        foreach (array_slice($rows, 0, 3) as $row) {
            if (!is_array($row)) {
                continue;
            }
            $domain = trim((string) ($row['referring_domain'] ?? ''));
            $issue = trim((string) ($row['issue_type'] ?? $row['health'] ?? ''));
            if ($domain === '') {
                continue;
            }
            $examples[] = $domain . ($issue !== '' ? ' (' . $issue . ')' : '');
        }
        $exampleText = implode(', ', $examples);

        return match ($language) {
            'ro' => 'Am terminat auditul backlinkurilor. În acest moment văd ' . $count . ' backlinkuri analizate din ' . $domains . ' domenii, cu ' . $broken . ' linkuri rupte, ' . $high . ' riscuri mari și ' . $medium . ' riscuri medii.' . ($exampleText !== '' ? ' Exemple utile: ' . $exampleText . '.' : ''),
            'bg' => 'Приключих backlink audit-а. В момента виждам ' . $count . ' анализирани backlinks от ' . $domains . ' домейна, с ' . $broken . ' счупени линка, ' . $high . ' high risk и ' . $medium . ' medium risk.' . ($exampleText !== '' ? ' Полезни примери: ' . $exampleText . '.' : ''),
            default => 'I finished the backlink audit. Right now I see ' . $count . ' analyzed backlinks from ' . $domains . ' referring domains, with ' . $broken . ' broken links, ' . $high . ' high-risk items, and ' . $medium . ' medium-risk items.' . ($exampleText !== '' ? ' Useful examples: ' . $exampleText . '.' : ''),
        };
    }

    private function aysa_adjustments_build_missing_pages_completion_message($snapshot, $language = 'en') {
        $count = intval($snapshot['count'] ?? 0);
        if ($count <= 0) {
            return $this->aysa_adjustments_tr(
                $language,
                'I refreshed the missing pages view and I do not currently see saved page gaps.',
                'Am reîmprospătat missing pages și momentan nu văd goluri de pagini salvate.',
                'Опресних missing pages изгледа и в момента не виждам запазени липси на страници.'
            );
        }

        return match ($language) {
            'ro' => 'Am reîmprospătat missing pages și am identificat ' . $count . ' keyword-uri sau planuri care încă nu au o pagină potrivită. Din lista asta putem decide ce pagini noi merită create și ce poate fi acoperit prin paginile existente.',
            'bg' => 'Опресних missing pages и идентифицирах ' . $count . ' keyword-а или плана, които все още нямат подходяща страница. От този списък можем да решим кои нови страници си струва да бъдат създадени и какво може да се покрие от съществуващите.',
            default => 'I refreshed missing pages and identified ' . $count . ' keywords or planned items that still do not have a suitable page. From this list we can decide what new pages are worth creating and what can still be covered by existing pages.',
        };
    }

    private function aysa_adjustments_build_content_strategy_completion_message($snapshot, $language = 'en') {
        $total = intval($snapshot['total'] ?? 0);
        $generated = intval($snapshot['generated'] ?? 0);
        $published = intval($snapshot['published'] ?? 0);
        $pending = intval($snapshot['pending'] ?? 0);

        if ($total <= 0) {
            return $this->aysa_adjustments_tr(
                $language,
                'I refreshed the content strategy view, but I do not have saved strategy rows yet.',
                'Am reîmprospătat content strategy, dar încă nu am rânduri salvate în strategie.',
                'Опресних content strategy изгледа, но все още нямам запазени редове в стратегията.'
            );
        }

        return match ($language) {
            'ro' => 'Am reîmprospătat content strategy și acum am ' . $total . ' idei sau piese de plan salvate. Dintre ele, ' . $generated . ' sunt deja generate, ' . $published . ' sunt deja legate de pagini, iar ' . $pending . ' sunt încă în așteptare. Putem folosi asta ca să decidem ce conținut merită făcut mai departe.',
            'bg' => 'Опресних content strategy и сега имам ' . $total . ' запазени идеи или стратегически елементи. От тях ' . $generated . ' вече са генерирани, ' . $published . ' вече са свързани със страници, а ' . $pending . ' все още са в изчакване. Можем да използваме това, за да решим какво съдържание си струва да се прави нататък.',
            default => 'I refreshed the content strategy and I now have ' . $total . ' saved ideas or strategy items. Of these, ' . $generated . ' are already generated, ' . $published . ' are already tied to pages, and ' . $pending . ' are still pending. We can use this to decide what content is worth producing next.',
        };
    }

    private function aysa_adjustments_tr($language, $en, $ro, $bg) {
        switch ($language) {
            case 'ro':
                return $ro;
            case 'bg':
                return $bg;
            default:
                return $en;
        }
    }

    private function aysa_reset_local_project_runtime($project_id) {
        $project_id = intval($project_id);
        $breakdown = array(
            'keyword_metrics_deleted' => 0,
            'monitoring_rank_history_deleted' => 0,
            'keyword_v2_snapshots_deleted' => 0,
            'content_strategies_deleted' => 0,
            'missing_page_meta_deleted' => 0,
            'pending_reviews_deleted' => 0,
            'onpage_inventory_deleted' => 0,
            'onpage_projection_runs_deleted' => 0,
            'options_deleted' => 0,
            'transients_deleted' => 0,
            'scheduled_events_cleared' => 0,
        );

        if ($project_id <= 0) {
            return $breakdown;
        }

        $database_breakdown = $this->aysa_reset_local_project_database_state($project_id);
        foreach ($database_breakdown as $key => $value) {
            if (array_key_exists($key, $breakdown)) {
                $breakdown[$key] = intval($value);
            }
        }

        $option_keys = array(
            $this->aysa_keyword_discovery_status_key($project_id),
            $this->aysa_keyword_discovery_snapshot_key($project_id),
            $this->get_keyword_discovery_local_associations_option_key($project_id),
            $this->aysa_monitoring_refresh_status_key($project_id),
            $this->aysa_monitoring_refresh_queue_key($project_id),
            $this->aysa_keyword_tracking_plan_option_key($project_id),
            $this->aysa_adjustments_get_recent_relevant_keywords_option_key($project_id),
            $this->aysa_adjustments_pending_approvals_option_key($project_id),
            $this->aysa_adjustments_generation_queue_key($project_id),
            $this->aysa_competitor_gap_cache_key($project_id),
            $this->aysa_competitor_identification_cache_key($project_id),
            'aysa_backlink_summary_' . $project_id,
            $this->aysa_get_backlink_audit_key($project_id),
            $this->aysa_get_backlink_audit_sync_key($project_id),
            $this->aysa_get_backlink_strategy_key($project_id),
            $this->aysa_get_backlink_strategy_sync_key($project_id),
            $this->aysa_get_backlink_audit_domain_key($project_id, (string) wp_parse_url(home_url('/'), PHP_URL_HOST)),
            $this->aysa_get_adverlink_link_issues_key($project_id),
            $this->aysa_get_adverlink_link_issues_sync_key($project_id),
            $this->aysa_get_adverlink_orders_key($project_id),
            $this->aysa_technical_site_audit_state_key($project_id),
            $this->aysa_technical_site_audit_issue_lifecycle_key($project_id),
            $this->aysa_technical_pagespeed_state_key($project_id),
            $this->aysa_technical_schema_state_key($project_id),
            $this->aysa_technical_redirects_state_key($project_id),
            $this->aysa_technical_sitemap_robots_state_key($project_id),
            $this->aysa_technical_sitemap_generation_state_key(),
            $this->aysa_technical_internal_linking_preview_state_key($project_id),
            'aysa_content_strategy_hard_reset_' . $project_id,
        );

        foreach ($option_keys as $key) {
            if (!$key) {
                continue;
            }
            if (delete_option($key)) {
                $breakdown['options_deleted']++;
            }
            wp_cache_delete($key, 'options');
        }

        $transient_keys = array(
            $this->get_suggested_keywords_cache_key($project_id),
            $this->aysa_keyword_discovery_lock_key($project_id),
            $this->aysa_monitoring_refresh_lock_key($project_id),
        );

        foreach ($transient_keys as $key) {
            if ($key && delete_transient($key)) {
                $breakdown['transients_deleted']++;
            }
        }

        global $wpdb;
        if (isset($wpdb) && is_object($wpdb)) {
            $like_patterns = array(
                'aysa_keyword_discovery_%_' . $project_id,
                'aysa_monitoring_%_' . $project_id,
                'aysa_adjustments_%_' . $project_id,
                'aysa_competitor_%_' . $project_id,
                'aysa_backlink_%_' . $project_id,
                'aysa_technical_%_' . $project_id,
                'aysa_adverlink_%_' . $project_id,
            );
            foreach ($like_patterns as $pattern) {
                $breakdown['options_deleted'] += $this->aysa_agent8_factory_reset_delete_options_like($pattern);
            }

            // Domain-scoped audit caches put the project id in the middle of
            // the option name, so the legacy suffix patterns above cannot
            // match them. Clear the exact project namespace on a full reset.
            $backlink_domain_cache_pattern = $wpdb->esc_like('aysa_backlink_audit_domain_' . $project_id . '_') . '%';
            $breakdown['options_deleted'] += $this->aysa_agent8_factory_reset_delete_options_like($backlink_domain_cache_pattern);
        }

        wp_cache_delete('alloptions', 'options');
        wp_cache_delete('notoptions', 'options');

        while (false !== ($ts = wp_next_scheduled('aysa_keyword_discovery_refresh_background_event', array($project_id)))) {
            wp_unschedule_event($ts, 'aysa_keyword_discovery_refresh_background_event', array($project_id));
            $breakdown['scheduled_events_cleared']++;
        }
        while (false !== ($ts = wp_next_scheduled('aysa_monitoring_rank_refresh_background_event', array($project_id)))) {
            wp_unschedule_event($ts, 'aysa_monitoring_rank_refresh_background_event', array($project_id));
            $breakdown['scheduled_events_cleared']++;
        }
        while (false !== ($ts = wp_next_scheduled('aysa_monitoring_rank_refresh_cron_tick', array($project_id)))) {
            wp_unschedule_event($ts, 'aysa_monitoring_rank_refresh_cron_tick', array($project_id));
            $breakdown['scheduled_events_cleared']++;
        }

        return $breakdown;
    }

    private function aysa_reset_local_project_database_state($project_id) {
        global $wpdb;

        $project_id = intval($project_id);
        $breakdown = array(
            'keyword_metrics_deleted' => 0,
            'monitoring_rank_history_deleted' => 0,
            'keyword_v2_snapshots_deleted' => 0,
            'content_strategies_deleted' => 0,
            'missing_page_meta_deleted' => 0,
            'pending_reviews_deleted' => 0,
            'onpage_inventory_deleted' => 0,
            'onpage_projection_runs_deleted' => 0,
        );

        if ($project_id <= 0 || !isset($wpdb) || !is_object($wpdb)) {
            return $breakdown;
        }

        $table_map = array(
            'keyword_metrics_deleted' => $this->aysa_keyword_discovery_table(),
            'monitoring_rank_history_deleted' => $this->aysa_monitoring_history_table(),
            'keyword_v2_snapshots_deleted' => $this->aysa_keyword_v2_snapshot_table_name(),
            'content_strategies_deleted' => $wpdb->prefix . 'aysa_content_strategies',
            'missing_page_meta_deleted' => $this->aysa_missing_page_meta_table(),
            'pending_reviews_deleted' => $wpdb->prefix . 'aysa_automated_pending_reviews',
            'onpage_inventory_deleted' => $wpdb->prefix . 'aysa_onpage_inventory',
            'onpage_projection_runs_deleted' => $wpdb->prefix . 'aysa_onpage_projection_runs',
        );

        foreach ($table_map as $key => $table_name) {
            $table_name = esc_sql((string) $table_name);
            if ($table_name === '') {
                continue;
            }

            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table_name));
            if ($exists !== $table_name) {
                continue;
            }

            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work. The table identifier comes only from the fixed AYSA reset allowlist.
            $deleted_rows = $wpdb->query(
                $wpdb->prepare(
                    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                    "DELETE FROM {$table_name} WHERE project_id = %d",
                    $project_id
                )
            );
            if ($deleted_rows !== false) {
                $breakdown[$key] = intval($deleted_rows);
            }
        }

        return $breakdown;
    }

    private function aysa_reset_local_setup_state($user_id, $project_id = 0) {
        $user_id = intval($user_id);
        $project_id = intval($project_id);
        $steps = array('integrations', 'general', 'objectives', 'seo-settings', 'competition', 'tone-of-voice');
        $deleted = 0;

        foreach ($steps as $step) {
            if ($user_id > 0 && delete_option('aysa_wizard_' . $step . '_' . $user_id)) {
                $deleted++;
            }
            if ($project_id > 0 && delete_option('aysa_wizard_' . $step . '_project_' . $project_id)) {
                $deleted++;
            }
        }

        return $deleted;
    }

    public function handle_agent8_factory_reset_dry_run() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }

        $target = $this->aysa_agent8_factory_reset_detect_target();
        $guard = $this->aysa_agent8_factory_reset_validate_target($target);
        if (empty($guard['ok'])) {
            wp_send_json_error(array(
                'message' => 'Factory Reset dry-run aborted: ' . (string) ($guard['reason'] ?? 'unsafe context'),
                'target' => $target,
                'guard' => $guard,
            ));
            return;
        }
        if (empty($target['project_id']) || empty($target['site_host'])) {
            wp_send_json_error(array(
                'message' => 'Factory Reset dry-run target is incomplete.',
                'target' => $target,
            ));
            return;
        }

        $wordpress = $this->aysa_agent8_factory_reset_wordpress_dry_run($target);
        $api_result = $this->aysa_broker_request('POST', '/v1/conversation/apprentice/factory-reset-dry-run', array(
            'project_id' => intval($target['project_id']),
            'site_host' => (string) $target['site_host'],
            'wp_user_id' => intval($target['wp_user_id'] ?? 0),
            'aysa_user_id' => intval($target['aysa_user_id'] ?? 0),
            'channels' => $this->aysa_agent8_factory_reset_channels(),
        ), 45);
        if (is_wp_error($api_result)) {
            wp_send_json_error(array(
                'message' => 'Factory Reset dry-run aborted: API cleanup preview failed. ' . $api_result->get_error_message(),
                'target' => $target,
                'guard' => $guard,
            ));
            return;
        }
        if (empty($api_result['ok'])) {
            wp_send_json_error(array(
                'message' => 'Factory Reset dry-run aborted: API cleanup preview was not accepted.',
                'target' => $target,
                'guard' => $guard,
                'api' => $api_result,
            ));
            return;
        }
        $api_result['channels_requested'] = $this->aysa_agent8_factory_reset_channels();
        $api_result['agent8_v3_lab_chat_cleanup_required'] = true;
        $api_result['skipped'] = false;
        $api_result['message'] = 'API factory reset dry-run includes Agent8 V3 Lab chat cleanup.';
        $snapshot_payload = array(
            'ok' => true,
            'mode' => 'dry_run',
            'target' => $target,
            'current_context' => $target,
            'guard' => $guard,
            'confirmation_phrase' => 'RESET AYSA PRE ACTIVATION ' . intval($target['project_id']) . ' ' . (string) $target['site_host'],
            'wordpress' => $wordpress,
            'api' => $api_result,
            'preserve' => $this->aysa_agent8_factory_reset_preserve_dry_run(),
            'safety' => array(
                'dry_run_only' => true,
                'deletes' => 0,
                'updates' => 0,
                'reset_executed' => false,
            ),
        );
        $snapshot_payload = $this->aysa_agent8_factory_reset_attach_snapshot($snapshot_payload);

        wp_send_json_success($snapshot_payload);
    }

    public function handle_agent8_factory_reset_execute() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }

        $token = isset($_POST['token']) ? sanitize_text_field((string) wp_unslash($_POST['token'])) : '';
        $phrase = isset($_POST['confirmation_phrase']) ? trim(sanitize_text_field((string) wp_unslash($_POST['confirmation_phrase']))) : '';
        $snapshot = $this->aysa_agent8_factory_reset_get_snapshot($token);
        if (!is_array($snapshot)) {
            wp_send_json_error(array('message' => 'Factory Reset execute aborted: dry-run snapshot is missing or expired.'));
            return;
        }
        if (time() > intval($snapshot['expires_at'] ?? 0)) {
            $this->aysa_agent8_factory_reset_delete_snapshot($token);
            wp_send_json_error(array('message' => 'Factory Reset execute aborted: dry-run snapshot expired.'));
            return;
        }
        if ($phrase === '' || $phrase !== (string) ($snapshot['confirmation_phrase'] ?? '')) {
            wp_send_json_error(array('message' => 'Factory Reset execute aborted: confirmation phrase does not match.'));
            return;
        }

        $target = $this->aysa_agent8_factory_reset_detect_target();
        $guard = $this->aysa_agent8_factory_reset_validate_target($target);
        if (empty($guard['ok'])) {
            wp_send_json_error(array(
                'message' => 'Factory Reset execute aborted: ' . (string) ($guard['reason'] ?? 'unsafe context'),
                'target' => $target,
                'guard' => $guard,
            ));
            return;
        }
        if (!$this->aysa_agent8_factory_reset_same_context($snapshot['target'] ?? array(), $target)) {
            wp_send_json_error(array('message' => 'Factory Reset execute aborted: current context changed since dry-run.'));
            return;
        }

        $current_api = $this->aysa_broker_request('POST', '/v1/conversation/apprentice/factory-reset-dry-run', array(
            'project_id' => intval($target['project_id']),
            'site_host' => (string) $target['site_host'],
            'wp_user_id' => intval($target['wp_user_id'] ?? 0),
            'aysa_user_id' => intval($target['aysa_user_id'] ?? 0),
            'channels' => $this->aysa_agent8_factory_reset_channels(),
        ), 45);
        if (is_wp_error($current_api)) {
            wp_send_json_error(array('message' => 'Factory Reset execute aborted: API cleanup preview failed. ' . $current_api->get_error_message()));
            return;
        }
        if (empty($current_api['ok'])) {
            wp_send_json_error(array('message' => 'Factory Reset execute aborted: API cleanup preview was not accepted.', 'api' => $current_api));
            return;
        }

        $current_counts = $this->aysa_agent8_factory_reset_snapshot_counts($this->aysa_agent8_factory_reset_wordpress_dry_run($target), $current_api);
        $expected_counts = $snapshot['counts'] ?? array();
        if (($current_counts['wordpress'] ?? array()) !== ($expected_counts['wordpress'] ?? array()) || ($current_counts['api'] ?? array()) !== ($expected_counts['api'] ?? array())) {
            wp_send_json_error(array(
                'message' => 'Factory Reset execute aborted: dry-run counts changed. Run dry-run again.',
                'expected_counts' => $expected_counts,
                'current_counts' => $current_counts,
            ));
            return;
        }

        $api_execute = $this->aysa_broker_request('POST', '/v1/conversation/apprentice/factory-reset-execute', array(
            'project_id' => intval($target['project_id']),
            'site_host' => (string) $target['site_host'],
            'wp_user_id' => intval($target['wp_user_id'] ?? 0),
            'aysa_user_id' => intval($target['aysa_user_id'] ?? 0),
            'channels' => $this->aysa_agent8_factory_reset_channels(),
            'expected_counts' => $this->aysa_agent8_factory_reset_flat_api_counts($expected_counts),
        ), 180);
        if (is_wp_error($api_execute)) {
            wp_send_json_error(array('message' => 'Factory Reset execute aborted: API cleanup failed. ' . $api_execute->get_error_message()));
            return;
        }
        if (empty($api_execute['ok'])) {
            wp_send_json_error(array('message' => 'Factory Reset execute aborted: API cleanup was not accepted.', 'api' => $api_execute));
            return;
        }

        $wp_execute = $this->aysa_agent8_factory_reset_wordpress_execute($target);
        $this->aysa_agent8_factory_reset_delete_snapshot($token);

        wp_send_json_success(array(
            'ok' => true,
            'mode' => 'execute',
            'target' => $target,
            'api' => $api_execute,
            'wordpress' => $wp_execute,
            'reload' => true,
        ));
    }

    public function handle_agent8_factory_reset_execute_fast() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }

        $target = $this->aysa_agent8_factory_reset_detect_local_allowlisted_target();
        $guard = $this->aysa_agent8_factory_reset_validate_target($target);
        if (empty($guard['ok'])) {
            wp_send_json_error(array(
                'message' => 'Reset la început blocat: ' . (string) ($guard['reason'] ?? 'unsafe context'),
                'target' => $target,
                'guard' => $guard,
            ));
            return;
        }

        $phrase = isset($_POST['confirmation_phrase']) ? trim(sanitize_text_field((string) wp_unslash($_POST['confirmation_phrase']))) : '';
        $expected_phrase = 'RESET AYSA PRE ACTIVATION ' . intval($target['project_id']) . ' ' . (string) $target['site_host'];
        if ($phrase === '' || $phrase !== $expected_phrase) {
            wp_send_json_error(array('message' => 'Reset la început blocat: fraza de confirmare nu se potrivește.'));
            return;
        }

        $wp_execute = $this->aysa_agent8_factory_reset_wordpress_execute($target);

        wp_send_json_success(array(
            'ok' => true,
            'mode' => 'execute_fast',
            'target' => $target,
            'confirmation_phrase' => $expected_phrase,
            'api' => array(
                'ok' => false,
                'skipped' => true,
                'message' => 'Fast local pre-activation reset does not call remote API runtime.',
            ),
            'wordpress' => $wp_execute,
            'reload' => true,
        ));
    }

    private function aysa_agent8_factory_reset_detect_target() {
        $site_host = wp_parse_url(home_url('/'), PHP_URL_HOST);
        $site_host = is_string($site_host) ? strtolower(trim($site_host)) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $request_site_host = isset($_POST['site_host']) ? strtolower(trim(sanitize_text_field((string) wp_unslash($_POST['site_host'])))) : '';
        $auth_domain = strtolower(trim((string) get_option('aysa_auth_domain', '')));
        $wp_user_id = intval(get_current_user_id());
        $auth_manager = class_exists('Aysa_Auth_Manager') ? Aysa_Auth_Manager::get_instance() : null;
        $auth_status = is_object($auth_manager) && method_exists($auth_manager, 'is_authenticated')
            ? $auth_manager->is_authenticated()
            : array();
        $aysa_user_id = isset($auth_status['aysa_user_id']) ? intval($auth_status['aysa_user_id']) : 0;
        if ($aysa_user_id <= 0) {
            $aysa_user_id = intval(get_option('aysa_user_id', 0));
        }
        if ($aysa_user_id <= 0) {
            $aysa_user_id = intval($this->get_user_id());
        }

        $project_id = intval(get_option('aysa_project_id', 0));
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $request_project_raw = isset($_POST['project_id']) ? sanitize_text_field((string) wp_unslash($_POST['project_id'])) : '';
        $request_project_id = intval($request_project_raw);
        if ($request_project_id <= 0 && preg_match('/^lab_(\d+)$/', $request_project_raw, $matches)) {
            $request_project_id = intval($matches[1]);
        }
        $website_id = 0;
        $validation = array('valid' => false);
        $matched_allowlisted_target = null;
        if ($request_project_id > 0 && $request_site_host !== '') {
            foreach ($this->aysa_agent8_factory_reset_allowed_targets() as $allowed_target) {
                if (intval($allowed_target['project_id'] ?? 0) !== $request_project_id) {
                    continue;
                }
                if (strtolower(trim((string) ($allowed_target['site_host'] ?? ''))) !== $request_site_host) {
                    continue;
                }
                $matched_allowlisted_target = $allowed_target;
                break;
            }
        }
        if (is_array($matched_allowlisted_target)) {
            if ($project_id <= 0) {
                $project_id = $request_project_id;
            }
            $website_id = intval($matched_allowlisted_target['website_id'] ?? 0);
            $validation = array(
                'valid' => true,
                'project_id' => $request_project_id,
                'website_id' => $website_id,
                'code' => 'local_allowlisted_factory_reset',
            );
        } else {
            $validation = $this->aysa_validate_test_access_key($this->aysa_get_effective_access_key(''), $site_host);
            if (!empty($validation['valid'])) {
                if ($project_id <= 0) {
                    $project_id = intval($validation['project_id'] ?? 0);
                }
                $website_id = intval($validation['website_id'] ?? 0);
            }
        }

        return array(
            'project_id' => $project_id,
            'website_id' => $website_id,
            'site_host' => $site_host,
            'wp_user_id' => $wp_user_id,
            'aysa_user_id' => $aysa_user_id,
            'home_url' => home_url('/'),
            'request_project_id' => $request_project_id,
            'request_site_host' => $request_site_host,
            'auth_domain' => $auth_domain,
            'auth_status' => array(
                'authenticated' => !empty($auth_status['authenticated']),
                'source' => isset($auth_status['source']) ? sanitize_key((string) $auth_status['source']) : '',
            ),
            'access_key_validation' => array(
                'valid' => !empty($validation['valid']),
                'project_id' => intval($validation['project_id'] ?? 0),
                'website_id' => intval($validation['website_id'] ?? 0),
                'code' => isset($validation['code']) ? sanitize_key((string) $validation['code']) : '',
            ),
        );
    }

    private function aysa_agent8_factory_reset_detect_local_allowlisted_target() {
        $site_host = wp_parse_url(home_url('/'), PHP_URL_HOST);
        $site_host = is_string($site_host) ? strtolower(trim($site_host)) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $request_site_host = isset($_POST['site_host']) ? strtolower(trim(sanitize_text_field((string) wp_unslash($_POST['site_host'])))) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $request_project_raw = isset($_POST['project_id']) ? sanitize_text_field((string) wp_unslash($_POST['project_id'])) : '';
        $request_project_id = intval($request_project_raw);
        if ($request_project_id <= 0 && preg_match('/^lab_(\d+)$/', $request_project_raw, $matches)) {
            $request_project_id = intval($matches[1]);
        }

        $matched_allowlisted_target = null;
        foreach ($this->aysa_agent8_factory_reset_allowed_targets() as $allowed_target) {
            if (intval($allowed_target['project_id'] ?? 0) !== $request_project_id) {
                continue;
            }
            if (strtolower(trim((string) ($allowed_target['site_host'] ?? ''))) !== $site_host) {
                continue;
            }
            if ($request_site_host !== '' && $request_site_host !== $site_host) {
                continue;
            }
            $matched_allowlisted_target = $allowed_target;
            break;
        }

        $project_id = is_array($matched_allowlisted_target) ? intval($matched_allowlisted_target['project_id'] ?? 0) : $request_project_id;
        $website_id = is_array($matched_allowlisted_target) ? intval($matched_allowlisted_target['website_id'] ?? 0) : 0;
        $wp_user_id = intval(get_current_user_id());
        $aysa_user_id = intval(get_option('aysa_user_id', 0));

        return array(
            'project_id' => $project_id,
            'website_id' => $website_id,
            'site_host' => $site_host,
            'wp_user_id' => $wp_user_id,
            'aysa_user_id' => $aysa_user_id,
            'home_url' => home_url('/'),
            'request_project_id' => $request_project_id,
            'request_site_host' => $request_site_host,
            'auth_domain' => strtolower(trim((string) get_option('aysa_auth_domain', ''))),
            'auth_status' => array(
                'authenticated' => false,
                'source' => 'local_allowlisted_factory_reset',
            ),
            'access_key_validation' => array(
                'valid' => is_array($matched_allowlisted_target),
                'project_id' => $project_id,
                'website_id' => $website_id,
                'code' => is_array($matched_allowlisted_target) ? 'local_allowlisted_factory_reset' : 'not_allowlisted',
            ),
        );
    }

    private function aysa_agent8_factory_reset_validate_target($target) {
        $project_id = intval($target['project_id'] ?? 0);
        $website_id = intval($target['website_id'] ?? 0);
        $site_host = strtolower(trim((string) ($target['site_host'] ?? '')));
        $request_project_id = intval($target['request_project_id'] ?? 0);
        $request_site_host = strtolower(trim((string) ($target['request_site_host'] ?? '')));
        $auth_domain = strtolower(trim((string) ($target['auth_domain'] ?? '')));
        $validation_project_id = intval($target['access_key_validation']['project_id'] ?? 0);

        if ($project_id <= 0) {
            return array('ok' => false, 'reason' => 'missing project_id');
        }
        if ($site_host === '') {
            return array('ok' => false, 'reason' => 'missing site_host');
        }
        if ($request_project_id > 0 && $request_project_id !== $project_id) {
            return array('ok' => false, 'reason' => 'request project_id does not match detected project_id');
        }
        if ($validation_project_id > 0 && $validation_project_id !== $project_id) {
            return array('ok' => false, 'reason' => 'access key project_id does not match detected project_id');
        }
        if ($request_site_host !== '' && $request_site_host !== $site_host) {
            return array('ok' => false, 'reason' => 'request site_host does not match detected site_host');
        }
        if ($auth_domain !== '' && $auth_domain !== $site_host) {
            return array('ok' => false, 'reason' => 'auth domain does not match detected site_host');
        }
        if (!$this->aysa_agent8_factory_reset_target_allowed($project_id, $website_id, $site_host)) {
            return array('ok' => false, 'reason' => 'project is not allowlisted for Factory Reset test flow');
        }

        return array(
            'ok' => true,
            'scope' => array(
                'project_ids' => array($project_id, 0),
                'site_host' => $site_host,
                'project_zero_requires_site_host_match' => true,
                'confirmation_phrase' => 'RESET AYSA PRE ACTIVATION ' . $project_id . ' ' . $site_host,
            ),
        );
    }

    private function aysa_agent8_factory_reset_allowed_targets() {
        $targets = array();

        /**
         * Allows controlled QA environments to opt in specific Agent8 projects.
         * Each target should include project_id, website_id and site_host.
         */
        $targets = apply_filters('aysa_agent8_factory_reset_allowed_targets', $targets);
        if (!is_array($targets)) {
            return array();
        }

        $normalized = array();
        foreach ($targets as $target) {
            if (!is_array($target)) {
                continue;
            }
            $project_id = intval($target['project_id'] ?? 0);
            $website_id = intval($target['website_id'] ?? 0);
            $site_host = strtolower(trim((string) ($target['site_host'] ?? '')));
            if ($project_id <= 0 || $site_host === '') {
                continue;
            }
            $normalized[] = array(
                'project_id' => $project_id,
                'website_id' => $website_id,
                'site_host' => $site_host,
                'label' => sanitize_text_field((string) ($target['label'] ?? $site_host)),
            );
        }

        return $normalized;
    }

    private function aysa_agent8_factory_reset_target_allowed($project_id, $website_id, $site_host) {
        $project_id = intval($project_id);
        $website_id = intval($website_id);
        $site_host = strtolower(trim((string) $site_host));
        if ($project_id <= 0 || $site_host === '') {
            return false;
        }

        foreach ($this->aysa_agent8_factory_reset_allowed_targets() as $target) {
            $target_project_id = intval($target['project_id'] ?? 0);
            $target_website_id = intval($target['website_id'] ?? 0);
            $target_site_host = strtolower(trim((string) ($target['site_host'] ?? '')));
            if ($target_project_id !== $project_id || $target_site_host !== $site_host) {
                continue;
            }
            if ($target_website_id > 0 && $website_id > 0 && $target_website_id !== $website_id) {
                continue;
            }
            return true;
        }

        return false;
    }

    private function aysa_agent8_factory_reset_attach_snapshot($payload) {
        $ttl = 10 * MINUTE_IN_SECONDS;
        $token = wp_generate_password(40, false, false);
        $expires_at = time() + $ttl;
        $snapshot = array(
            'target' => $payload['target'] ?? array(),
            'counts' => $this->aysa_agent8_factory_reset_snapshot_counts($payload['wordpress'] ?? array(), $payload['api'] ?? array()),
            'confirmation_phrase' => (string) ($payload['confirmation_phrase'] ?? ''),
            'api' => $payload['api'] ?? array(),
            'created_at' => time(),
            'expires_at' => $expires_at,
        );
        set_transient($this->aysa_agent8_factory_reset_snapshot_key($token), $snapshot, $ttl);
        $payload['execute'] = array(
            'token' => $token,
            'expires_at' => $expires_at,
            'expires_in_minutes' => 10,
        );
        $payload['counts'] = $snapshot['counts'];
        return $payload;
    }

    private function aysa_agent8_factory_reset_snapshot_key($token) {
        return 'aysa_factory_reset_snapshot_' . hash('sha256', (string) $token);
    }

    private function aysa_agent8_factory_reset_get_snapshot($token) {
        if ((string) $token === '') {
            return null;
        }
        $snapshot = get_transient($this->aysa_agent8_factory_reset_snapshot_key($token));
        return is_array($snapshot) ? $snapshot : null;
    }

    private function aysa_agent8_factory_reset_delete_snapshot($token) {
        if ((string) $token !== '') {
            delete_transient($this->aysa_agent8_factory_reset_snapshot_key($token));
        }
    }

    private function aysa_agent8_factory_reset_same_context($expected, $current) {
        foreach (array('project_id', 'website_id', 'site_host', 'wp_user_id', 'aysa_user_id') as $key) {
            $left = isset($expected[$key]) ? (string) $expected[$key] : '';
            $right = isset($current[$key]) ? (string) $current[$key] : '';
            if ($left !== $right) {
                return false;
            }
        }
        return true;
    }

    private function aysa_agent8_factory_reset_snapshot_counts($wordpress, $api) {
        $conversation = is_array($api) && isset($api['conversation']) && is_array($api['conversation']) ? $api['conversation'] : array();
        $research = is_array($api) && isset($api['research']) && is_array($api['research']) ? $api['research'] : array();
        $agent8_state = is_array($api) && isset($api['agent8_state']) && is_array($api['agent8_state']) ? $api['agent8_state'] : array();
        $wp_tables = is_array($wordpress) && isset($wordpress['custom_tables']) && is_array($wordpress['custom_tables']) ? $wordpress['custom_tables'] : array();
        $wp_custom_rows = 0;
        foreach ($wp_tables as $row) {
            $wp_custom_rows += intval($row['count'] ?? 0);
        }

        return array(
            'wordpress' => array(
                'options' => intval($wordpress['options']['count'] ?? 0),
                'user_meta' => intval($wordpress['user_meta']['count'] ?? 0),
                'custom_table_rows' => $wp_custom_rows,
            ),
            'api' => array(
                'sessions' => intval($conversation['project_conversation_sessions']['count'] ?? 0),
                'messages' => intval($conversation['project_conversation_messages']['count'] ?? 0),
                'action_history' => intval($conversation['project_action_history']['count'] ?? 0),
                'notifications' => intval($conversation['project_notifications']['count'] ?? 0),
                'knowledge_docs' => intval($conversation['project_knowledge_docs']['count'] ?? 0),
                'rag_requests' => intval($conversation['project_rag_requests']['count'] ?? 0),
                'research_runs' => intval($research['runs']['count'] ?? 0),
                'research_tasks' => intval($research['tasks']['count'] ?? 0),
                'research_events' => intval($research['events']['count'] ?? 0),
                'worker_project_files' => intval($conversation['worker_project_storage']['file_count'] ?? 0),
                'worker_task_artifacts' => intval($research['worker_artifacts']['count'] ?? 0),
                'agent8_state_versions' => intval($agent8_state['count'] ?? 0),
            ),
        );
    }

    private function aysa_agent8_factory_reset_flat_api_counts($counts) {
        $api_counts = is_array($counts) && isset($counts['api']) && is_array($counts['api']) ? $counts['api'] : array();
        $keys = array(
            'sessions',
            'messages',
            'action_history',
            'notifications',
            'knowledge_docs',
            'rag_requests',
            'research_runs',
            'research_tasks',
            'research_events',
            'worker_project_files',
            'worker_task_artifacts',
            'agent8_state_versions',
        );
        $flat = array();
        foreach ($keys as $key) {
            $flat[$key] = intval($api_counts[$key] ?? 0);
        }
        return $flat;
    }

    private function aysa_agent8_factory_reset_channels() {
        return array(
            'agent8_v3_lab',
            'agent8',
            'setup_seo_profile',
            'adjustments_and_improvements',
        );
    }

    private function aysa_agent8_factory_reset_wordpress_dry_run($target) {
        global $wpdb;

        $project_id = intval($target['project_id'] ?? 0);
        $wp_user_id = intval($target['wp_user_id'] ?? 0);
        $aysa_user_id = intval($target['aysa_user_id'] ?? 0);
        $site_host = (string) ($target['site_host'] ?? '');
        $steps = array('integrations', 'general', 'objectives', 'seo-settings', 'competition', 'tone-of-voice');

        $option_names = array(
            $this->aysa_test_access_key_option_name(),
            'aysa_active_access_key',
            'aysa_user_secret_key',
            'aysa_user_id',
            'aysa_auth_domain',
            'aysa_project_id',
            'aysa_website_id',
            'aysa_wallet_app_internal_secret',
            'aysa_wallet_app_internal_secret_version',
            'aysa_wallet_app_internal_secret_scope',
            'aysa_setup_completed',
        );
        if ($project_id > 0) {
            $option_names[] = 'aysa_onboarding_run_token_project_' . $project_id;
            foreach ($steps as $step) {
                $option_names[] = 'aysa_wizard_' . $step . '_project_' . $project_id;
            }
            foreach (array('research', 'technical', 'onpage', 'offpage', 'monitoring') as $domain) {
                $option_names[] = $this->aysa_agent8_canonical_state_option_key($domain, $project_id);
            }
            $option_names = array_merge($option_names, $this->aysa_agent8_factory_reset_runtime_option_names($project_id));
            $option_names[] = $this->aysa_get_backlink_audit_domain_key($project_id, $site_host);
        }
        if ($aysa_user_id > 0) {
            foreach ($steps as $step) {
                $option_names[] = 'aysa_wizard_' . $step . '_' . $aysa_user_id;
            }
        }
        $option_names = array_values(array_unique(array_filter(array_map('strval', $option_names))));

        $options = array();
        foreach ($option_names as $name) {
            $exists = false;
            $bytes = 0;
            if (isset($wpdb) && is_object($wpdb)) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $raw = $wpdb->get_var($wpdb->prepare("SELECT option_value FROM {$wpdb->options} WHERE option_name = %s LIMIT 1", $name));
                $exists = $raw !== null;
                $bytes = is_string($raw) ? strlen($raw) : 0;
            } else {
                $value = get_option($name, null);
                $exists = $value !== null;
                $bytes = strlen((string) maybe_serialize($value));
            }
            $options[] = array(
                'option_name' => $name,
                'exists' => $exists,
                'bytes' => $bytes,
            );
        }

        $like_patterns = array();
        if ($project_id > 0) {
            $like_patterns = array(
                'aysa_keyword_discovery_%_' . $project_id,
                'aysa_monitoring_%_' . $project_id,
                'aysa_adjustments_%_' . $project_id,
                'aysa_competitor_%_' . $project_id,
                'aysa_backlink_%_' . $project_id,
                $wpdb->esc_like('aysa_backlink_audit_domain_' . $project_id . '_') . '%',
                'aysa_technical_%_' . $project_id,
                'aysa_adverlink_%_' . $project_id,
            );
        }

        return array(
            'options' => array(
                'count' => count(array_filter($options, static function ($row) { return !empty($row['exists']); })),
                'items' => $options,
                'like_patterns' => $this->aysa_agent8_factory_reset_option_like_dry_run($like_patterns),
            ),
            'user_meta' => $this->aysa_agent8_factory_reset_user_meta_dry_run($wp_user_id, $site_host),
            'custom_tables' => $this->aysa_agent8_factory_reset_wp_table_dry_run($project_id, $wp_user_id, $site_host),
        );
    }

    private function aysa_agent8_factory_reset_runtime_option_names($project_id) {
        return array(
            $this->aysa_keyword_discovery_status_key($project_id),
            $this->aysa_keyword_discovery_snapshot_key($project_id),
            $this->get_keyword_discovery_local_associations_option_key($project_id),
            $this->aysa_monitoring_refresh_status_key($project_id),
            $this->aysa_monitoring_refresh_queue_key($project_id),
            $this->aysa_keyword_tracking_plan_option_key($project_id),
            $this->aysa_adjustments_get_recent_relevant_keywords_option_key($project_id),
            $this->aysa_adjustments_pending_approvals_option_key($project_id),
            $this->aysa_adjustments_generation_queue_key($project_id),
            $this->aysa_competitor_gap_cache_key($project_id),
            $this->aysa_competitor_identification_cache_key($project_id),
            'aysa_backlink_summary_' . $project_id,
            $this->aysa_get_backlink_audit_key($project_id),
            $this->aysa_get_backlink_audit_sync_key($project_id),
            $this->aysa_get_backlink_strategy_key($project_id),
            $this->aysa_get_backlink_strategy_sync_key($project_id),
            $this->aysa_get_adverlink_link_issues_key($project_id),
            $this->aysa_get_adverlink_link_issues_sync_key($project_id),
            $this->aysa_get_adverlink_orders_key($project_id),
            $this->aysa_technical_site_audit_state_key($project_id),
            $this->aysa_technical_site_audit_issue_lifecycle_key($project_id),
            $this->aysa_technical_pagespeed_state_key($project_id),
            $this->aysa_technical_schema_state_key($project_id),
            $this->aysa_technical_redirects_state_key($project_id),
            $this->aysa_technical_sitemap_robots_state_key($project_id),
            $this->aysa_technical_sitemap_generation_state_key(),
            $this->aysa_technical_internal_linking_preview_state_key($project_id),
            'aysa_content_strategy_hard_reset_' . $project_id,
        );
    }

    private function aysa_agent8_factory_reset_option_like_dry_run($patterns) {
        global $wpdb;
        $rows = array();
        if (!isset($wpdb) || !is_object($wpdb)) {
            return $rows;
        }
        foreach ($patterns as $pattern) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $count = intval($wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name LIKE %s", $pattern)));
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $samples = $wpdb->get_col($wpdb->prepare("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s ORDER BY option_name ASC LIMIT 10", $pattern));
            $rows[] = array(
                'pattern' => $pattern,
                'count' => $count,
                'sample_option_names' => is_array($samples) ? array_values($samples) : array(),
            );
        }
        return $rows;
    }

    private function aysa_agent8_factory_reset_user_meta_dry_run($wp_user_id, $site_host) {
        global $wpdb;
        $meta_key = $this->aysa_agent8_storage_key($wp_user_id, $site_host);
        if ($wp_user_id <= 0 || !isset($wpdb) || !is_object($wpdb)) {
            return array('count' => 0, 'items' => array());
        }
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $raw = $wpdb->get_var($wpdb->prepare("SELECT meta_value FROM {$wpdb->usermeta} WHERE user_id = %d AND meta_key = %s LIMIT 1", $wp_user_id, $meta_key));
        return array(
            'count' => $raw === null ? 0 : 1,
            'items' => array(array(
                'user_id' => $wp_user_id,
                // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reviewed WordPress query: the metadata selector is required by the feature and is constrained by the surrounding post type, status, limit, or administrative scope.
                'meta_key' => $meta_key,
                'exists' => $raw !== null,
                'bytes' => is_string($raw) ? strlen($raw) : 0,
            )),
        );
    }

    private function aysa_agent8_factory_reset_wp_table_dry_run($project_id, $wp_user_id, $site_host) {
        global $wpdb;
        $rows = array();
        if (!isset($wpdb) || !is_object($wpdb)) {
            return $rows;
        }

        $project_tables = array(
            'keyword_metrics' => $this->aysa_keyword_discovery_table(),
            'monitoring_rank_history' => $this->aysa_monitoring_history_table(),
            'keyword_v2_snapshots' => $this->aysa_keyword_v2_snapshot_table_name(),
            'content_strategies' => $wpdb->prefix . 'aysa_content_strategies',
            'missing_page_meta' => $this->aysa_missing_page_meta_table(),
            'pending_reviews' => $wpdb->prefix . 'aysa_automated_pending_reviews',
            'onpage_inventory' => $wpdb->prefix . 'aysa_onpage_inventory',
            'onpage_projection_runs' => $wpdb->prefix . 'aysa_onpage_projection_runs',
        );
        foreach ($project_tables as $label => $table) {
            $rows[] = $this->aysa_agent8_factory_reset_count_table_where($table, $label, 'project_id = %d', array($project_id));
        }

        $auth_table = esc_sql($wpdb->prefix . Aysa_Auth_Manager::TABLE_NAME);
        $rows[] = $this->aysa_agent8_factory_reset_count_table_where(
            $auth_table,
            'aysa_user_auth',
            'domain = %s',
            array($site_host)
        );

        return $rows;
    }

    private function aysa_agent8_factory_reset_count_table_where($table, $label, $where_sql, $where_args) {
        global $wpdb;
        $table = (string) $table;
        if ($table === '' || !isset($wpdb) || !is_object($wpdb)) {
            return array('label' => $label, 'table' => $table, 'exists' => false, 'count' => 0, 'sample_ids' => array());
        }
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table)) === $table;
        if (!$exists) {
            return array('label' => $label, 'table' => $table, 'exists' => false, 'count' => 0, 'sample_ids' => array());
        }
        $count_sql = "SELECT COUNT(*) FROM {$table} WHERE {$where_sql}";
        $sample_sql = "SELECT id FROM {$table} WHERE {$where_sql} ORDER BY id DESC LIMIT 10";
        return array(
            'label' => $label,
            'table' => $table,
            'exists' => true,
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query: table identifiers and structure are internal, and request-derived values are prepared before execution. Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            'count' => intval($wpdb->get_var($wpdb->prepare($count_sql, $where_args))),
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Reviewed release query: table identifiers and structure are internal, and request-derived values are prepared before execution.
            'sample_ids' => array_map('intval', (array) $wpdb->get_col($wpdb->prepare($sample_sql, $where_args))),
        );
    }

    private function aysa_agent8_factory_reset_wordpress_execute($target) {
        $dry_run = $this->aysa_agent8_factory_reset_wordpress_dry_run($target);
        $deleted_options = 0;
        $deleted_like_options = 0;
        $deleted_user_meta = 0;
        $deleted_custom_rows = 0;

        foreach ((array) ($dry_run['options']['items'] ?? array()) as $row) {
            $name = isset($row['option_name']) ? (string) $row['option_name'] : '';
            if ($name !== '') {
                if (!empty($row['exists']) && delete_option($name)) {
                    $deleted_options++;
                }
                // A persistent object cache can retain a stale option even when
                // its database row is already absent. Full reset must evict it.
                wp_cache_delete($name, 'options');
            }
        }
        foreach ((array) ($dry_run['options']['like_patterns'] ?? array()) as $row) {
            $pattern = isset($row['pattern']) ? (string) $row['pattern'] : '';
            if ($pattern !== '') {
                $deleted_like_options += $this->aysa_agent8_factory_reset_delete_options_like($pattern);
            }
        }
        wp_cache_delete('alloptions', 'options');
        wp_cache_delete('notoptions', 'options');

        $wp_user_id = intval($target['wp_user_id'] ?? 0);
        $site_host = (string) ($target['site_host'] ?? '');
        if ($wp_user_id > 0 && $site_host !== '') {
            $deleted_user_meta += delete_user_meta($wp_user_id, $this->aysa_agent8_storage_key($wp_user_id, $site_host));
        }

        $project_id = intval($target['project_id'] ?? 0);
        if ($project_id > 0) {
            $deleted_custom_rows += $this->aysa_agent8_factory_reset_delete_wp_project_rows($project_id);
        }
        if ($wp_user_id > 0 && $site_host !== '' && class_exists('Aysa_Auth_Manager')) {
            global $wpdb;
            $auth_table = esc_sql($wpdb->prefix . Aysa_Auth_Manager::TABLE_NAME);
            $deleted_custom_rows += $this->aysa_agent8_factory_reset_delete_table_where(
                $auth_table,
                'domain = %s',
                array($site_host)
            );
        }
        $this->aysa_clear_activation_binding($wp_user_id);

        return array(
            'deleted_options' => $deleted_options,
            'deleted_like_options' => $deleted_like_options,
            'deleted_user_meta' => $deleted_user_meta,
            'deleted_custom_rows' => $deleted_custom_rows,
        );
    }

    private function aysa_agent8_factory_reset_delete_options_like($pattern) {
        global $wpdb;
        if (!isset($wpdb) || !is_object($wpdb) || (string) $pattern === '') {
            return 0;
        }
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $names = $wpdb->get_col($wpdb->prepare("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s", $pattern));
        $deleted = 0;
        foreach ((array) $names as $name) {
            if (delete_option((string) $name)) {
                $deleted++;
            }
            wp_cache_delete((string) $name, 'options');
        }
        wp_cache_delete('alloptions', 'options');
        wp_cache_delete('notoptions', 'options');
        return $deleted;
    }

    private function aysa_agent8_factory_reset_delete_wp_project_rows($project_id) {
        global $wpdb;
        if (!isset($wpdb) || !is_object($wpdb)) {
            return 0;
        }
        $tables = array(
            $this->aysa_keyword_discovery_table(),
            $this->aysa_monitoring_history_table(),
            $this->aysa_keyword_v2_snapshot_table_name(),
            $wpdb->prefix . 'aysa_content_strategies',
            $this->aysa_missing_page_meta_table(),
            $wpdb->prefix . 'aysa_automated_pending_reviews',
            $wpdb->prefix . 'aysa_onpage_inventory',
            $wpdb->prefix . 'aysa_onpage_projection_runs',
        );
        $deleted = 0;
        foreach ($tables as $table) {
            $deleted += $this->aysa_agent8_factory_reset_delete_table_where($table, 'project_id = %d', array($project_id));
        }
        return $deleted;
    }

    private function aysa_agent8_factory_reset_delete_table_where($table, $where_sql, $where_args) {
        global $wpdb;
        $table = (string) $table;
        if ($table === '' || !isset($wpdb) || !is_object($wpdb)) {
            return 0;
        }
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table)) === $table;
        if (!$exists) {
            return 0;
        }
        $sql = "DELETE FROM {$table} WHERE {$where_sql}";
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query: table identifiers and structure are internal, and request-derived values are prepared before execution. Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $result = $wpdb->query($wpdb->prepare($sql, $where_args));
        return $result === false ? 0 : intval($result);
    }

    private function aysa_agent8_factory_reset_preserve_dry_run() {
        $post_counts = array(
            'posts' => wp_count_posts('post'),
            'pages' => wp_count_posts('page'),
            'products' => post_type_exists('product') ? wp_count_posts('product') : null,
            'media' => wp_count_posts('attachment'),
            'shop_orders' => post_type_exists('shop_order') ? wp_count_posts('shop_order') : null,
        );

        return array(
            'wordpress_content' => array(
                'status' => 'preserved',
                'counts' => $post_counts,
            ),
            'woocommerce_orders_customers' => array(
                'status' => 'preserved',
                'orders_table_not_touched' => true,
                'customers_table_not_touched' => true,
            ),
            'applied_seo_meta' => array(
                'status' => 'preserved',
                'postmeta_not_touched' => true,
            ),
            'live_redirects_htaccess' => array(
                'status' => 'preserved',
                'htaccess_not_touched' => true,
            ),
            'billing_wallet_ledger' => array(
                'status' => 'preserved',
                'wallet_tables_not_touched' => true,
            ),
            'global_config_secrets' => array(
                'status' => 'preserved',
                'secure_config_not_touched' => true,
            ),
            'other_projects_sites' => array(
                'status' => 'preserved',
                'scope' => 'current detected project/site only',
            ),
        );
    }

    public function handle_apprentice_reset_test() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }
        if (!$this->aysa_qa_controls_enabled()) {
            wp_send_json_error(array('message' => 'QA controls are unavailable.'), 404);
            return;
        }
        $wp_user_id = get_current_user_id();
        $site_host = isset($_POST['site_host']) ? sanitize_text_field(wp_unslash($_POST['site_host'])) : '';
        if (!$this->check_authentication(false)) {
            $this->aysa_agent8_clear_session($wp_user_id, $site_host);
            wp_send_json_success(array(
                'message' => 'Agent8 pre-activation session reset completed.',
                'pre_activation' => true,
            ));
            return;
        }

        $key_validation = $this->aysa_validate_test_access_key(
            $this->aysa_get_effective_access_key(isset($_POST['access_key']) ? sanitize_text_field(wp_unslash($_POST['access_key'])) : ''),
            isset($_POST['site_host']) ? sanitize_text_field(wp_unslash($_POST['site_host'])) : ''
        );
        if (empty($key_validation['valid'])) {
            wp_send_json_error(array(
                'message' => (string) ($key_validation['message'] ?? 'Access key is invalid.'),
                'code' => 'access_key_invalid',
            ));
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0);
        $auth_manager = Aysa_Auth_Manager::get_instance();
        $auth_status = $auth_manager->is_authenticated();
        $user_id = isset($auth_status['aysa_user_id']) ? intval($auth_status['aysa_user_id']) : 0;

        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Missing reset context.'));
            return;
        }

        $setup_options_deleted = $this->aysa_reset_local_setup_state($user_id, $project_id);
        $this->aysa_agent8_clear_session($wp_user_id, $site_host);
        $onboarding_run_token = $this->aysa_rotate_onboarding_run_token($project_id);
        delete_option('aysa_setup_completed');
        $this->aysa_mark_setup_reset_forced();
        $oauth_disconnect = $this->aysa_broker_request('POST', '/v1/oauth/disconnect', array(
            'project_id' => $project_id,
        ), 30);
        $local_breakdown = $this->aysa_reset_local_project_runtime($project_id);

        $result = $this->aysa_broker_request('POST', '/v1/conversation/apprentice/reset', array(
            'project_id' => $project_id,
            'site_host' => $site_host,
            'channels' => array('agent8', 'setup_seo_profile', 'adjustments_and_improvements'),
            'clear_keyword_research_runs' => true,
        ), 45);

        if (is_wp_error($result)) {
            wp_send_json_error(array('message' => $result->get_error_message()));
            return;
        }

        $this->aysa_clear_activation_binding($wp_user_id);

        wp_send_json_success(array(
            'message' => 'Apprentice test reset completed.',
            'setup_options_deleted' => $setup_options_deleted,
            'onboarding_run_token' => $onboarding_run_token,
            'local' => $local_breakdown,
            'oauth_disconnect' => is_wp_error($oauth_disconnect)
                ? array('ok' => false, 'message' => $oauth_disconnect->get_error_message())
                : array('ok' => true, 'result' => $oauth_disconnect),
            'result' => $result,
        ));
    }

    public function handle_setup_reset_chat_test() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }
        if (!$this->aysa_qa_controls_enabled()) {
            wp_send_json_error(array('message' => 'QA controls are unavailable.'), 404);
            return;
        }
        $key_validation = $this->aysa_validate_test_access_key(
            $this->aysa_get_effective_access_key(isset($_POST['access_key']) ? sanitize_text_field(wp_unslash($_POST['access_key'])) : ''),
            isset($_POST['site_host']) ? sanitize_text_field(wp_unslash($_POST['site_host'])) : ''
        );
        if (empty($key_validation['valid'])) {
            wp_send_json_error(array(
                'message' => (string) ($key_validation['message'] ?? 'Access key is invalid.'),
                'code' => 'access_key_invalid',
            ));
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0);
        $auth_manager = Aysa_Auth_Manager::get_instance();
        $auth_status = $auth_manager->is_authenticated();
        $user_id = isset($auth_status['aysa_user_id']) ? intval($auth_status['aysa_user_id']) : 0;

        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Missing reset context.'));
            return;
        }

        $setup_options_deleted = $this->aysa_reset_local_setup_state($user_id, $project_id);
        $onboarding_run_token = $this->aysa_rotate_onboarding_run_token($project_id);
        delete_option('aysa_setup_completed');
        $this->aysa_mark_setup_reset_forced();

        $result = $this->aysa_broker_request('POST', '/v1/conversation/setup/reset', array(
            'project_id' => $project_id,
            'site_host' => isset($_POST['site_host']) ? sanitize_text_field(wp_unslash($_POST['site_host'])) : '',
        ), 45);

        if (is_wp_error($result)) {
            wp_send_json_error(array('message' => $result->get_error_message()));
            return;
        }

        wp_send_json_success(array(
            'message' => 'Setup chat test reset completed.',
            'setup_options_deleted' => $setup_options_deleted,
            'onboarding_run_token' => $onboarding_run_token,
            'result' => $result,
        ));
    }

    private function aysa_setup_normalize_url($value) {
        $value = trim((string) $value);
        if ($value === '') {
            return '';
        }
        if (!preg_match('#^[a-z][a-z0-9+.-]*://#i', $value)) {
            $value = 'https://' . ltrim($value, '/');
        }
        $host = wp_parse_url($value, PHP_URL_HOST);
        if (!is_string($host) || trim($host) === '') {
            return '';
        }
        $scheme = wp_parse_url($value, PHP_URL_SCHEME);
        if (!is_string($scheme) || $scheme === '') {
            $scheme = 'https';
        }
        return strtolower($scheme) . '://' . $host;
    }

    private function aysa_setup_is_local_host($host) {
        $host = strtolower(trim((string) $host));
        if ($host === '') {
            return false;
        }
        if ($host === 'localhost' || $host === '127.0.0.1' || $host === '::1') {
            return true;
        }
        if (substr($host, -10) === '.localhost') {
            return true;
        }
        if (substr($host, -6) === '.local') {
            return true;
        }
        return false;
    }

    private function aysa_get_local_setup_state_for_user($user_id, $project_id = 0) {
        $user_id = intval($user_id);
        $project_id = intval($project_id);
        if ($user_id <= 0 && $project_id <= 0) {
            return array();
        }

        $steps = array('integrations', 'general', 'objectives', 'seo-settings', 'competition', 'tone-of-voice');
        $state = array();
        foreach ($steps as $step) {
            $project_raw = $project_id > 0 ? get_option('aysa_wizard_' . $step . '_project_' . $project_id, null) : null;
            $raw = $user_id > 0 ? get_option('aysa_wizard_' . $step . '_' . $user_id, array()) : array();
            if ($step === 'general') {
                $state[$step] = $this->aysa_choose_best_general_setup_state($project_raw, $raw);
                continue;
            }
            if (is_array($project_raw) && !empty($project_raw)) {
                $state[$step] = $project_raw;
                continue;
            }
            $state[$step] = is_array($raw) ? $raw : array();
        }
        return $state;
    }

    private function aysa_choose_best_general_setup_state($project_raw, $user_raw) {
        $project_state = is_array($project_raw) ? $project_raw : array();
        $user_state = is_array($user_raw) ? $user_raw : array();
        if (empty($project_state)) {
            return $user_state;
        }
        if (empty($user_state)) {
            return $project_state;
        }

        $project_score = $this->aysa_general_setup_completeness_score($project_state);
        $user_score = $this->aysa_general_setup_completeness_score($user_state);
        $best = ($user_score > $project_score) ? $user_state : $project_state;
        $fallback = ($best === $user_state) ? $project_state : $user_state;

        foreach ($fallback as $key => $value) {
            if (!is_string($key)) {
                continue;
            }
            if (!array_key_exists($key, $best) || trim((string) $best[$key]) === '') {
                $best[$key] = $value;
            }
        }

        return $best;
    }

    private function aysa_general_setup_completeness_score($state) {
        if (!is_array($state)) {
            return 0;
        }
        $score = 0;
        foreach (array('company_name', 'brand_name', 'support_phone', 'website', 'website_type', 'shipping', 'payment_method', 'industry', 'company_info') as $field) {
            if (trim((string) ($state[$field] ?? '')) !== '') {
                $score++;
            }
        }
        return $score;
    }

    private function aysa_general_setup_validated_true($state) {
        if (!is_array($state)) {
            return false;
        }
        $validated = $state['validated'] ?? false;
        return $validated === true || $validated === 1 || $validated === '1' || $validated === 'true';
    }

    private function aysa_guard_general_setup_persistence($incoming, $project_existing, $user_existing) {
        $incoming = is_array($incoming) ? $incoming : array();
        $project_existing = is_array($project_existing) ? $project_existing : array();
        $user_existing = is_array($user_existing) ? $user_existing : array();

        $best_existing = $this->aysa_choose_best_general_setup_state($project_existing, $user_existing);
        $incoming_score = $this->aysa_general_setup_completeness_score($incoming);
        $existing_score = $this->aysa_general_setup_completeness_score($best_existing);

        if ($existing_score > $incoming_score) {
            $guarded = $best_existing;
            foreach ($incoming as $key => $value) {
                if (!is_string($key)) {
                    continue;
                }
                if (!array_key_exists($key, $guarded) || trim((string) $guarded[$key]) === '') {
                    $guarded[$key] = $value;
                }
            }
        } else {
            $guarded = $incoming;
        }

        if (
            $this->aysa_general_setup_validated_true($incoming)
            || $this->aysa_general_setup_validated_true($project_existing)
            || $this->aysa_general_setup_validated_true($user_existing)
        ) {
            $guarded['validated'] = '1';
        }

        return $guarded;
    }

    private function aysa_merge_local_setup_state_fallback($existing, $incoming) {
        if (!is_array($existing)) {
            $existing = array();
        }
        if (!is_array($incoming) || empty($incoming)) {
            return $existing;
        }

        foreach ($incoming as $key => $value) {
            if (!is_string($key)) {
                $existing[$key] = $value;
                continue;
            }

            if (!array_key_exists($key, $existing)) {
                $existing[$key] = $value;
                continue;
            }

            if (is_array($value) && is_array($existing[$key]) && $this->aysa_is_assoc_array($existing[$key]) && $this->aysa_is_assoc_array($value)) {
                $existing[$key] = $this->aysa_merge_local_setup_state_fallback($existing[$key], $value);
            }
        }

        return $existing;
    }

    private function aysa_is_assoc_array($value) {
        if (!is_array($value)) {
            return false;
        }
        if ($value === array()) {
            return false;
        }
        return array_keys($value) !== range(0, count($value) - 1);
    }

    private function aysa_apply_setup_conversation_updates($user_id, $updates_by_step, $project_id = 0) {
        $user_id = intval($user_id);
        $project_id = intval($project_id);
        if (($user_id <= 0 && $project_id <= 0) || !is_array($updates_by_step)) {
            return;
        }

        foreach ($updates_by_step as $step => $data) {
            $step_key = sanitize_key((string) $step);
            if (!is_array($data) || $step_key === '') {
                continue;
            }

            $option_name = 'aysa_wizard_' . $step_key . '_' . $user_id;
            $existing = get_option($option_name, array());
            if (!is_array($existing)) {
                $existing = array();
            }

            $sanitized = $this->sanitize_wizard_data($data);
            if ($project_id > 0) {
                $project_option_name = 'aysa_wizard_' . $step_key . '_project_' . $project_id;
                $project_existing = get_option($project_option_name, array());
                if (!is_array($project_existing)) {
                    $project_existing = array();
                }
                update_option($project_option_name, $sanitized, false);
            }

            if ($user_id > 0) {
                update_option($option_name, $sanitized, false);
            }
        }
    }

    private function aysa_persist_local_setup_state_snapshot($user_id, $local_setup_state, $project_id = 0) {
        $user_id = intval($user_id);
        $project_id = intval($project_id);
        if (($user_id <= 0 && $project_id <= 0) || !is_array($local_setup_state) || empty($local_setup_state)) {
            return;
        }

        $updates_by_step = array();
        foreach ($local_setup_state as $step => $data) {
            $step_key = sanitize_key((string) $step);
            if ($step_key === '' || !is_array($data) || empty($data)) {
                continue;
            }
            $updates_by_step[$step_key] = $this->sanitize_wizard_data($data);
        }

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

        foreach ($updates_by_step as $step_key => $sanitized) {
            if ($step_key === 'general') {
                $project_existing = $project_id > 0 ? get_option('aysa_wizard_general_project_' . $project_id, array()) : array();
                $user_existing = $user_id > 0 ? get_option('aysa_wizard_general_' . $user_id, array()) : array();
                $sanitized = $this->aysa_guard_general_setup_persistence($sanitized, $project_existing, $user_existing);
            }
            if ($project_id > 0) {
                $project_option_name = 'aysa_wizard_' . $step_key . '_project_' . $project_id;
                update_option($project_option_name, $sanitized, false);
            }
            if ($user_id > 0) {
                $user_option_name = 'aysa_wizard_' . $step_key . '_' . $user_id;
                update_option($user_option_name, $sanitized, false);
            }
        }
    }

    private function aysa_sync_setup_state_to_client_rag($project_id, $conversation_language, $local_setup_state, $google_connected = false, $google_skipped = false, $state_meta = array()) {
        $project_id = intval($project_id);
        if ($project_id <= 0 || !is_array($local_setup_state) || empty($local_setup_state)) {
            return false;
        }

        $language = in_array((string) $conversation_language, array('en', 'ro', 'bg'), true) ? (string) $conversation_language : 'en';
        $general = isset($local_setup_state['general']) && is_array($local_setup_state['general']) ? $local_setup_state['general'] : array();
        $website = isset($general['website']) ? $this->aysa_setup_normalize_url((string) $general['website']) : '';
        if ($website === '') {
            $website = $this->aysa_setup_normalize_url(home_url('/'));
        }

        $state = array(
            'meta' => array_merge(array(
                'onboarding_mode' => 'strict_local',
                'google_optional_skipped' => !empty($google_skipped),
            ), is_array($state_meta) ? $state_meta : array()),
        );

        $result = $this->aysa_broker_request('POST', '/v1/conversation/adjustments/sync-setup-context', array(
            'project_id' => $project_id,
            'conversation_language' => $language,
            'website' => $website,
            'local_setup_state' => $local_setup_state,
            'research_snapshot' => array(),
            'ui_context' => array(
                'conversation_language' => $language,
                'available_languages' => array('en', 'ro', 'bg'),
                'phase' => 'agent_orchestration',
                'integrations' => array(
                    'google_connect_available' => true,
                    'google_connected' => !empty($google_connected),
                    'google_skipped' => !empty($google_skipped),
                ),
            ),
            'state' => $state,
        ), 45);

        return !is_wp_error($result);
    }

    private function aysa_adjustments_setup_steps() {
        return array(
            'integrations',
            'company_name',
            'brand_name',
            'support_phone',
            'website',
            'website_type',
            'shipping',
            'payment_method',
            'industry',
            'company_info',
            'objectives',
            'seed_keywords',
            'competitors',
            'seo_settings',
            'tone_of_voice',
        );
    }

    private function aysa_adjustments_is_skip_google_message($message) {
        $normalized = strtolower(trim((string) $message));
        foreach (array(
            'sari peste google',
            'sarim peste google',
            'skip google',
            'continue without google',
            'continue without google for now',
            'continue fara google',
            'continue fără google',
            'continui fara google',
            'continui fără google',
            'skip for now',
            'sari peste momentan',
            'fara google',
            'fără google',
        ) as $pattern) {
            if ($pattern !== '' && strpos($normalized, $pattern) !== false) {
                return true;
            }
        }
        return false;
    }

    private function aysa_adjustments_is_connect_google_message($message) {
        $normalized = mb_strtolower(trim((string) $message));
        foreach (array(
            'connect google',
            'connect with google',
            'conecteaza google',
            'conectează google',
            'conecteaza cu google',
            'conectează cu google',
            'hai sa conectam google',
            'hai să conectăm google',
            'vreau sa conectez google',
            'vreau să conectez google'
        ) as $pattern) {
            if ($pattern !== '' && strpos($normalized, $pattern) !== false) {
                return true;
            }
        }
        return false;
    }

    private function aysa_adjustments_is_continue_message($message) {
        $normalized = mb_strtolower(trim((string) $message));
        foreach (array(
            'continuam',
            'continuăm',
            'continue',
            'continue please',
            'mergem mai departe',
            'hai mai departe',
            'super, si ce fac',
            'super, și ce fac',
            'si ce fac',
            'și ce fac',
            'ce fac mai departe',
            'ce urmeaza',
            'ce urmează',
            'ok',
            'bine',
            'perfect',
            'super'
        ) as $pattern) {
            if ($pattern !== '' && $normalized === $pattern) {
                return true;
            }
        }
        return false;
    }

    private function aysa_setup_fetch_html($url) {
        $resp = wp_remote_get($url, array(
            'timeout' => 15,
            'redirection' => 5,
            'headers' => array(
                'Accept' => 'text/html,application/xhtml+xml',
                'User-Agent' => 'AYSA Setup Autofill/1.0',
            ),
        ));
        if (is_wp_error($resp)) {
            return '';
        }
        $code = intval(wp_remote_retrieve_response_code($resp));
        if ($code < 200 || $code >= 300) {
            return '';
        }
        return (string) wp_remote_retrieve_body($resp);
    }

    private function aysa_setup_extract_json_ld($html) {
        $out = array();
        if (!is_string($html) || $html === '') {
            return $out;
        }
        if (!preg_match_all('#<script[^>]*type=["\']application/ld\\+json["\'][^>]*>(.*?)</script>#is', $html, $matches)) {
            return $out;
        }
        foreach ($matches[1] as $raw) {
            $decoded = json_decode(trim((string) $raw), true);
            if (json_last_error() !== JSON_ERROR_NONE) {
                continue;
            }
            if (is_array($decoded)) {
                if (isset($decoded['@graph']) && is_array($decoded['@graph'])) {
                    foreach ($decoded['@graph'] as $node) {
                        if (is_array($node)) {
                            $out[] = $node;
                        }
                    }
                } else {
                    $out[] = $decoded;
                }
            }
        }
        return $out;
    }

    private function aysa_setup_extract_internal_links($website, $html) {
        $links = array();
        if (!is_string($html) || $html === '') {
            return $links;
        }

        $base_host = strtolower((string) wp_parse_url($website, PHP_URL_HOST));
        if ($base_host === '') {
            return $links;
        }
        $base_scheme = (string) wp_parse_url($website, PHP_URL_SCHEME);
        if ($base_scheme === '') {
            $base_scheme = 'https';
        }

        if (!preg_match_all('#<a[^>]+href=["\']([^"\']+)["\'][^>]*>#i', $html, $matches)) {
            return $links;
        }

        foreach ($matches[1] as $href) {
            $href = trim((string) $href);
            if ($href === '' || strpos($href, '#') === 0 || stripos($href, 'javascript:') === 0 || stripos($href, 'mailto:') === 0 || stripos($href, 'tel:') === 0) {
                continue;
            }

            $absolute = '';
            if (preg_match('#^https?://#i', $href)) {
                $absolute = $href;
            } elseif (strpos($href, '//') === 0) {
                $absolute = $base_scheme . ':' . $href;
            } elseif (strpos($href, '/') === 0) {
                $absolute = rtrim($website, '/') . $href;
            } else {
                $absolute = rtrim($website, '/') . '/' . ltrim($href, '/');
            }

            $host = strtolower((string) wp_parse_url($absolute, PHP_URL_HOST));
            if ($host === '' || $host !== $base_host) {
                continue;
            }

            $path = (string) wp_parse_url($absolute, PHP_URL_PATH);
            if ($path === '') {
                $path = '/';
            }
            $links[] = $base_scheme . '://' . $base_host . $path;
        }

        return array_values(array_unique($links));
    }

    private function aysa_setup_collect_candidate_urls($website, $homepage_html) {
        $urls = array(
            $website,
            rtrim($website, '/') . '/contact',
            rtrim($website, '/') . '/contact-us',
            rtrim($website, '/') . '/despre-noi',
            rtrim($website, '/') . '/about',
            rtrim($website, '/') . '/livrare',
            rtrim($website, '/') . '/shipping',
            rtrim($website, '/') . '/plata',
            rtrim($website, '/') . '/payment',
            rtrim($website, '/') . '/checkout',
            rtrim($website, '/') . '/termeni-si-conditii',
        );

        $priority_tokens = array(
            'contact', 'despre', 'about', 'livrare', 'shipping', 'plata', 'payment', 'checkout', 'termeni', 'faq', 'support'
        );
        $internal_links = $this->aysa_setup_extract_internal_links($website, $homepage_html);
        foreach ($internal_links as $link) {
            $path = strtolower((string) wp_parse_url($link, PHP_URL_PATH));
            foreach ($priority_tokens as $token) {
                if (strpos($path, $token) !== false) {
                    $urls[] = $link;
                    break;
                }
            }
            if (count($urls) >= 12) {
                break;
            }
        }

        return array_values(array_unique($urls));
    }

    private function aysa_setup_extract_general_info($website) {
        $host = (string) wp_parse_url($website, PHP_URL_HOST);
        $host_clean = strtolower(preg_replace('/^www\./', '', $host));
        $default_name = ucwords(str_replace(array('-', '_', '.'), ' ', (string) preg_replace('/\.[a-z]{2,}$/i', '', $host_clean)));

        $homepage_html = $this->aysa_setup_fetch_html($website);
        $candidate_urls = $this->aysa_setup_collect_candidate_urls($website, $homepage_html);

        $all_html_parts = array();
        $json_ld_nodes = array();
        $title = '';
        $description = '';
        $emails = array();
        $phones = array();

        foreach ($candidate_urls as $idx => $url) {
            $html = $idx === 0 ? $homepage_html : $this->aysa_setup_fetch_html($url);
            if (!is_string($html) || $html === '') {
                continue;
            }

            $all_html_parts[] = $html;

            if ($title === '' && preg_match('#<title[^>]*>(.*?)</title>#is', $html, $m)) {
                $title = trim(wp_strip_all_tags((string) $m[1]));
            }
            if ($description === '' && preg_match('#<meta[^>]+name=["\']description["\'][^>]+content=["\']([^"\']+)["\']#i', $html, $m)) {
                $description = trim((string) $m[1]);
            }

            $json_ld_nodes = array_merge($json_ld_nodes, $this->aysa_setup_extract_json_ld($html));

            if (preg_match_all('/[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}/i', $html, $email_matches)) {
                foreach ($email_matches[0] as $email) {
                    $email = strtolower(trim((string) $email));
                    if ($email !== '' && strpos($email, 'example.') === false && strpos($email, '@w3.org') === false) {
                        $emails[] = $email;
                    }
                }
            }
            if (preg_match_all('/(\+?\d[\d\-\s().]{7,}\d)/', $html, $phone_matches)) {
                foreach ($phone_matches[1] as $phone) {
                    $phone = trim((string) $phone);
                    $digits = preg_replace('/\D+/', '', $phone);
                    if (strlen((string) $digits) >= 8 && strlen((string) $digits) <= 15) {
                        $phones[] = $phone;
                    }
                }
            }
        }

        $json_name = '';
        $json_phone = '';
        $json_email = '';
        $json_description = '';
        foreach ($json_ld_nodes as $node) {
            if (!is_array($node)) {
                continue;
            }
            $type = strtolower((string) ($node['@type'] ?? ''));
            if ($type !== '' && strpos($type, 'organization') === false && strpos($type, 'localbusiness') === false && strpos($type, 'store') === false) {
                continue;
            }
            if ($json_name === '' && !empty($node['name'])) {
                $json_name = trim((string) $node['name']);
            }
            if ($json_phone === '' && !empty($node['telephone'])) {
                $json_phone = trim((string) $node['telephone']);
            }
            if ($json_email === '' && !empty($node['email'])) {
                $json_email = trim((string) $node['email']);
            }
            if ($json_description === '' && !empty($node['description'])) {
                $json_description = trim((string) $node['description']);
            }
        }

        $emails = array_values(array_unique($emails));
        $phones = array_values(array_unique($phones));
        $support_email = $json_email !== '' ? $json_email : (!empty($emails) ? (string) $emails[0] : '');
        $support_phone = $json_phone !== '' ? $json_phone : (!empty($phones) ? (string) $phones[0] : '');

        $all_text = strtolower(wp_strip_all_tags(implode("\n", $all_html_parts)));

        $shipping = '';
        if (strpos($all_text, 'livrare gratuita') !== false || strpos($all_text, 'free shipping') !== false) {
            $shipping = 'Free delivery options available';
        } elseif (strpos($all_text, 'livrare') !== false || strpos($all_text, 'shipping') !== false || strpos($all_text, 'curier') !== false) {
            $shipping = 'Delivery by courier/local shipping options available';
        }

        $payment_tokens = array();
        if (strpos($all_text, 'card') !== false) {
            $payment_tokens[] = 'Card';
        }
        if (strpos($all_text, 'paypal') !== false) {
            $payment_tokens[] = 'PayPal';
        }
        if (strpos($all_text, 'ramburs') !== false || strpos($all_text, 'cash on delivery') !== false) {
            $payment_tokens[] = 'Cash on delivery';
        }
        if (strpos($all_text, 'transfer bancar') !== false || strpos($all_text, 'bank transfer') !== false || strpos($all_text, 'op') !== false) {
            $payment_tokens[] = 'Bank transfer';
        }
        $payment_tokens = array_values(array_unique($payment_tokens));
        $payment = !empty($payment_tokens) ? implode(', ', $payment_tokens) : '';

        $website_type = 'business';
        if (strpos($all_text, 'add to cart') !== false || strpos($all_text, 'cos') !== false || strpos($all_text, 'checkout') !== false || strpos($all_text, 'produs') !== false || strpos($all_text, 'woocommerce') !== false) {
            $website_type = 'ecommerce';
        } elseif (strpos($all_text, 'blog') !== false || strpos($all_text, '/blog') !== false) {
            $website_type = 'blog';
        } elseif (strpos($all_text, 'portfolio') !== false) {
            $website_type = 'portfolio';
        }

        $industry = 'retail';
        if (strpos($all_text, 'flori') !== false || strpos($all_text, 'flowers') !== false || strpos($all_text, 'florarie') !== false || strpos($all_text, 'buchet') !== false) {
            $industry = 'florist';
        } elseif (strpos($all_text, 'restaurant') !== false || strpos($all_text, 'hotel') !== false) {
            $industry = 'hospitality';
        } elseif (strpos($all_text, 'clinica') !== false || strpos($all_text, 'medical') !== false) {
            $industry = 'healthcare';
        } elseif (strpos($all_text, 'imobiliare') !== false || strpos($all_text, 'real estate') !== false) {
            $industry = 'real-estate';
        } elseif (strpos($all_text, 'software') !== false || strpos($all_text, 'saas') !== false) {
            $industry = 'technology';
        }

        $company_name = $json_name !== '' ? $json_name : ($title !== '' ? preg_replace('/\s*[\|\-–].*$/u', '', $title) : $default_name);
        $brand = $json_name !== '' ? $json_name : $company_name;
        $company_description = $json_description !== '' ? $json_description : $description;
        $business_information = $company_description !== '' ? $company_description : 'Please review and complete business details.';

        // Keep setup launchable for WooCommerce/e-commerce sites even when the homepage
        // does not expose payment labels clearly enough for string matching.
        if ($payment === '' && $website_type === 'ecommerce') {
            $payment = 'Card, Bank transfer, Cash on delivery';
        }

        return array(
            'id' => 0,
            'project' => array('name' => $default_name),
            'companyName' => $company_name,
            'brand' => $brand,
            'companyDescription' => $company_description,
            'supportPhoneNumber' => $support_phone,
            'supportEmailAddress' => $support_email,
            'websiteUrl' => $website,
            'websiteType' => $website_type,
            'shippingOptions' => $shipping,
            'paymentMethods' => $payment,
            'industry' => $industry,
            'businessInformation' => $business_information,
            'source' => 'setup_general_autofill',
            'scannedUrls' => $candidate_urls,
        );
    }

    private function aysa_test_access_key_option_name() {
        return 'aysa_active_access_key';
    }

    private function aysa_get_effective_access_key($provided_key = '') {
        $provided_key = trim((string) $provided_key);
        if ($provided_key !== '') {
            return $provided_key;
        }
        return trim((string) get_option($this->aysa_test_access_key_option_name(), ''));
    }

    private function aysa_is_access_key_valid_globally(&$validation = null) {
        $stored_key = trim((string) get_option($this->aysa_test_access_key_option_name(), ''));
        $validation = $this->aysa_validate_test_access_key($stored_key, '');
        if (!empty($validation['valid'])) {
            return true;
        }

        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }
        if (!class_exists('Aysa_Auth_Manager')) {
            return false;
        }

        // If the site already has a valid AYSA auth session, promote it to the
        // current WP admin and restore the site-level access key from that auth.
        $auth_status = Aysa_Auth_Manager::get_instance()->is_authenticated();
        $auth_secret_key = is_array($auth_status) ? trim((string) ($auth_status['secret_key'] ?? '')) : '';
        if (empty($auth_status['authenticated']) || $auth_secret_key === '') {
            return false;
        }

        $auth_validation = $this->aysa_validate_test_access_key($auth_secret_key, '');
        if (!empty($auth_validation['valid'])) {
            if ($stored_key !== $auth_secret_key) {
                update_option($this->aysa_test_access_key_option_name(), $auth_secret_key, false);
            }
            if (!empty($auth_validation['project_id'])) {
                update_option('aysa_project_id', intval($auth_validation['project_id']), false);
            }
            if (!empty($auth_validation['website_id'])) {
                update_option('aysa_website_id', intval($auth_validation['website_id']), false);
            }
            $validation = $auth_validation;
            $validation['source'] = 'site_auth_recovery';
            return true;
        }

        // Final fail-open for a site that was already authenticated but the key
        // verifier is temporarily inconsistent. This keeps menus accessible for
        // other admins on the same site instead of forcing reactivation.
        if ($stored_key !== $auth_secret_key) {
            update_option($this->aysa_test_access_key_option_name(), $auth_secret_key, false);
        }
        $validation = array(
            'valid' => true,
            'message' => 'Access key restored from active site authentication.',
            'code' => 'access_key_restored_from_site_auth',
            'host' => (string) wp_parse_url(home_url(), PHP_URL_HOST),
            'source' => 'site_auth_fail_open',
        );
        return true;
    }

    private function aysa_is_workspace_activated_for_admin_menu(&$validation = null) {
        if ($this->aysa_is_access_key_valid_globally($validation)) {
            return true;
        }

        $project_id = intval(get_option('aysa_project_id', 0));
        if ($project_id <= 0 && class_exists('Aysa_Wallet_Manager')) {
            $project_id = intval(Aysa_Wallet_Manager::get_instance()->get_project_id());
        }
        $website_id = intval(get_option('aysa_website_id', 0));
        if ($project_id <= 0) {
            return false;
        }

        $site_host = (string) wp_parse_url(home_url('/'), PHP_URL_HOST);
        $site_host = strtolower(preg_replace('/^www\./', '', trim($site_host)));
        $v3_session_activation = $this->aysa_agent8v3_current_session_has_activation($site_host);
        $project_site_binding = $project_id > 0 && $site_host !== '';
        $auth_domain = strtolower(preg_replace('/^www\./', '', trim((string) get_option('aysa_auth_domain', ''))));
        if (!$v3_session_activation && !$project_site_binding && $auth_domain !== '' && $site_host !== '' && $auth_domain !== $site_host) {
            return false;
        }

        $stored_key = trim((string) get_option($this->aysa_test_access_key_option_name(), ''));
        $user_secret_key = trim((string) get_option('aysa_user_secret_key', ''));
        $auth_status = array();
        if (class_exists('Aysa_Auth_Manager')) {
            $auth_manager = Aysa_Auth_Manager::get_instance();
            if (is_object($auth_manager) && method_exists($auth_manager, 'is_authenticated')) {
                $auth_status = $auth_manager->is_authenticated();
            }
        }

        $auth_secret_key = is_array($auth_status) ? trim((string) ($auth_status['secret_key'] ?? '')) : '';
        $auth_is_authenticated = is_array($auth_status) && !empty($auth_status['authenticated']);
        if ($stored_key === '' && $user_secret_key === '' && $auth_secret_key === '' && !$auth_is_authenticated && !$v3_session_activation && !$project_site_binding) {
            return false;
        }

        $auth_status_domain = is_array($auth_status)
            ? strtolower(preg_replace('/^www\./', '', trim((string) ($auth_status['domain'] ?? ''))))
            : '';
        if ($auth_status_domain !== '' && $site_host !== '' && $auth_status_domain !== $site_host) {
            return false;
        }

        $validation = array(
            'valid' => true,
            'message' => 'Workspace activation restored from saved Agent8 activation state.',
            'code' => 'workspace_activation_from_saved_agent8_state',
            'project_id' => $project_id,
            'website_id' => $website_id,
            'host' => $site_host,
            'source' => $v3_session_activation ? 'admin_menu_agent8v3_session_sync' : ($project_site_binding ? 'admin_menu_project_site_binding_sync' : 'admin_menu_activation_sync'),
        );
        return true;
    }

    private function aysa_agent8v3_current_session_has_activation($site_host) {
        $site_host = strtolower(preg_replace('/^www\./', '', trim((string) $site_host)));
        if ($site_host === '') {
            return false;
        }

        $session = $this->aysa_agent8_load_session(get_current_user_id(), $site_host, 'ro');
        if (!is_array($session) || $session === array()) {
            return false;
        }

        $phase = sanitize_key((string) ($session['phase'] ?? ''));
        if (in_array($phase, array('', 'welcome', 'pre_activation', 'activation_key_requested', 'activation_failed'), true)) {
            return false;
        }

        $state = isset($session['state']) && is_array($session['state']) ? $session['state'] : array();
        $decisions = isset($state['decisions']) && is_array($state['decisions']) ? $state['decisions'] : array();
        if (!empty($decisions['activation_verified']) || !empty($decisions['access_key_valid'])) {
            return true;
        }

        $context = isset($state['context']) && is_array($state['context']) ? $state['context'] : array();
        $activation = isset($context['activation']) && is_array($context['activation']) ? $context['activation'] : array();
        if (!empty($activation['verified']) || !empty($activation['valid']) || !empty($activation['project_id'])) {
            return true;
        }

        $conversation_id = trim((string) ($session['conversation_id'] ?? ''));
        return $phase !== 'conversation' && ($conversation_id !== '' || $state !== array());
    }


    private function aysa_is_setup_profile_completed_effectively(&$reason = null) {
        if ($this->aysa_is_setup_reset_forced()) {
            $reason = array(
                'code' => 'setup_reset_forced',
                'message' => 'Setup was explicitly reset and must start again from the setup flow.',
                'source' => 'forced_reset',
                'step' => 'integrations',
            );
            return false;
        }

        $current_reason = array();
        if ($this->aysa_is_setup_profile_completed_for_current_user($current_reason)) {
            $this->aysa_clear_setup_reset_forced();
            if (!get_option('aysa_setup_completed', false)) {
                update_option('aysa_setup_completed', true, false);
            }
            $reason = array(
                'code' => 'setup_completed_for_current_user',
                'message' => 'Setup is completed for the current authenticated user.',
                'source' => 'current_user',
            );
            return true;
        }

        $site_completed = get_option('aysa_setup_completed', false) ? true : false;
        $project_id = intval(get_option('aysa_project_id', 0));
        $website_id = intval(get_option('aysa_website_id', 0));

        if ($site_completed || $this->aysa_has_site_setup_evidence()) {
            $reason = array(
                'code' => 'setup_completed_for_site',
                'message' => 'Setup is completed for this site based on saved site evidence.',
                'source' => 'site_state',
            );
            return true;
        }

        $reason = is_array($current_reason) ? $current_reason : array();
        return false;
    }

    private function aysa_has_site_setup_evidence() {
        global $wpdb;
        if (!$wpdb || !isset($wpdb->options)) {
            return false;
        }

        $patterns = array(
            'aysa_wizard_general_%',
            'aysa_wizard_general_project_%',
            'aysa_wizard_objectives_%',
            'aysa_wizard_objectives_project_%',
            'aysa_wizard_seo-settings_%',
            'aysa_wizard_seo-settings_project_%',
            'aysa_wizard_competition_%',
            'aysa_wizard_competition_project_%',
            'aysa_wizard_tone-of-voice_%',
            'aysa_wizard_tone-of-voice_project_%',
        );
        foreach ($patterns as $pattern) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $option_name = $wpdb->get_var(
                $wpdb->prepare(
                    "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s AND option_value IS NOT NULL AND option_value <> '' ORDER BY option_id DESC LIMIT 1",
                    $pattern
                )
            );
            if (is_string($option_name) && $option_name !== '') {
                return true;
            }
        }

        return false;
    }

    private function aysa_get_test_access_keys() {
        if (!$this->aysa_qa_controls_enabled() || !defined('AYSA_QA_PLAN_KEYS') || !is_array(AYSA_QA_PLAN_KEYS)) {
            return array();
        }

        $keys = array();
        foreach (AYSA_QA_PLAN_KEYS as $key => $metadata) {
            $key = trim((string) $key);
            if ($key === '' || !is_array($metadata)) {
                continue;
            }
            $keys[$key] = array(
                'key_id' => sanitize_text_field((string) ($metadata['key_id'] ?? '')),
                'account_id' => sanitize_text_field((string) ($metadata['account_id'] ?? '')),
                'account_label' => sanitize_text_field((string) ($metadata['account_label'] ?? '')),
                'plan_code' => sanitize_key((string) ($metadata['plan_code'] ?? '')),
                'plan_price_eur' => max(0, intval($metadata['plan_price_eur'] ?? 0)),
            );
        }
        return $keys;
    }

    private function aysa_qa_controls_enabled() {
        if (!defined('AYSA_ENABLE_QA_CONTROLS') || AYSA_ENABLE_QA_CONTROLS !== true) {
            return false;
        }
        $site_host = (string) wp_parse_url(home_url(), PHP_URL_HOST);
        return $this->aysa_setup_is_local_host($site_host);
    }

    private function aysa_get_authenticated_external_user_id() {
        if (!class_exists('Aysa_Auth_Manager')) {
            return 0;
        }
        $auth_manager = Aysa_Auth_Manager::get_instance();
        $auth_status = $auth_manager->is_authenticated();
        if (empty($auth_status['authenticated'])) {
            return 0;
        }
        return isset($auth_status['aysa_user_id']) ? intval($auth_status['aysa_user_id']) : 0;
    }

    private function aysa_get_effective_setup_user_id() {
        $external_id = $this->aysa_get_authenticated_external_user_id();
        if ($external_id > 0) {
            return $external_id;
        }
        return function_exists('get_current_user_id') ? intval(get_current_user_id()) : 0;
    }

    private function aysa_has_non_empty_value($value) {
        if (is_array($value)) {
            return !empty($value);
        }
        return trim((string) $value) !== '';
    }

    private function aysa_is_setup_profile_completed_for_current_user(&$reason = null) {
        $reason = array();
        $user_id = $this->aysa_get_authenticated_external_user_id();
        if ($user_id <= 0) {
            $reason = array(
                'code' => 'auth_required',
                'step' => 'integrations',
                'message' => 'Authentication is required before completing setup.',
            );
            return false;
        }

        $general = get_option('aysa_wizard_general_' . $user_id, array());
        $objectives = get_option('aysa_wizard_objectives_' . $user_id, array());
        $seo_settings = get_option('aysa_wizard_seo-settings_' . $user_id, array());
        $competition = get_option('aysa_wizard_competition_' . $user_id, array());
        $tone = get_option('aysa_wizard_tone-of-voice_' . $user_id, array());

        $general_required = array(
            'company_name',
            'brand_name',
            'support_phone',
            'website',
            'website_type',
            'shipping',
            'payment_method',
            'industry',
            'company_info',
        );
        foreach ($general_required as $field) {
            if (!$this->aysa_has_non_empty_value(isset($general[$field]) ? $general[$field] : '')) {
                $reason = array(
                    'code' => 'general_incomplete',
                    'step' => 'general',
                    'message' => 'Please complete all required General Information fields.',
                );
                return false;
            }
        }

        if (empty($objectives['objectives']) || !is_array($objectives['objectives'])) {
            $reason = array(
                'code' => 'objectives_incomplete',
                'step' => 'objectives',
                'message' => 'Please select at least one objective.',
            );
            return false;
        }

        $seo_required = array(
            'targeted_search_engine',
            'targeted_country',
            'primary_language',
        );
        foreach ($seo_required as $field) {
            if (!$this->aysa_has_non_empty_value(isset($seo_settings[$field]) ? $seo_settings[$field] : '')) {
                $reason = array(
                    'code' => 'seo_settings_incomplete',
                    'step' => 'seo-settings',
                    'message' => 'Please complete required SEO settings.',
                );
                return false;
            }
        }

        $has_seed_keyword = false;
        if (is_array($competition)) {
            foreach ($competition as $key => $value) {
                if (strpos((string) $key, 'keyword') === 0 && $this->aysa_has_non_empty_value($value)) {
                    $has_seed_keyword = true;
                    break;
                }
            }
        }
        if (!$has_seed_keyword) {
            $reason = array(
                'code' => 'competition_incomplete',
                'step' => 'competition',
                'message' => 'Please add at least one seed keyword.',
            );
            return false;
        }

        $has_competitor = false;
        if (is_array($competition)) {
            foreach ($competition as $key => $value) {
                if (strpos((string) $key, 'competitor') === 0 && $this->aysa_has_non_empty_value($value)) {
                    $has_competitor = true;
                    break;
                }
            }
        }
        if (!$has_competitor) {
            $reason = array(
                'code' => 'competition_incomplete',
                'step' => 'competition',
                'message' => 'Please confirm at least one competitor.',
            );
            return false;
        }

        if (!$this->aysa_has_non_empty_value(isset($tone['tone']) ? $tone['tone'] : '')) {
            $reason = array(
                'code' => 'tone_incomplete',
                'step' => 'tone-of-voice',
                'message' => 'Please choose a tone of voice.',
            );
            return false;
        }

        $project_id = intval($general['project_id'] ?? 0);
        if ($project_id <= 0) {
            $project_id = intval(get_option('aysa_project_id', 0));
        }
        $google_error = '';
        if (!$this->aysa_is_google_connected_for_project($project_id, $google_error)) {
            $reason = array(
                'code' => 'google_not_connected',
                'step' => 'integrations',
                'message' => ($google_error !== '' ? $google_error : 'Google connection is required before setup can be completed.'),
            );
            return false;
        }

        return true;
    }

    private function aysa_is_google_connected_for_project($project_id, &$error_message = '') {
        $error_message = '';
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            $error_message = 'Project ID is missing. Complete Integrations first.';
            return false;
        }

        if (!class_exists('Aysa_Google_OAuth_Manager')) {
            $manager_file = AYSA_PLUGIN_DIR . 'includes/class-aysa-google-oauth-manager.php';
            if (!file_exists($manager_file)) {
                $error_message = 'Google integration manager is missing.';
                return false;
            }
            require_once $manager_file;
        }

        if (!class_exists('Aysa_Google_OAuth_Manager')) {
            $error_message = 'Google integration manager is unavailable.';
            return false;
        }

        $manager = Aysa_Google_OAuth_Manager::get_instance();
        if (!is_object($manager) || !method_exists($manager, 'get_status_for_project')) {
            $error_message = 'Cannot validate Google integration status.';
            return false;
        }

        $status = $manager->get_status_for_project($project_id);
        if (is_wp_error($status)) {
            $error_code = (string) $status->get_error_code();
            // Do not downgrade an otherwise working site to "setup incomplete"
            // just because the OAuth broker had a transient transport/problematic
            // response. Reserve the warning for explicit disconnected states.
            if (in_array($error_code, array('broker_invalid_response', 'broker_http_failed', 'broker_error'), true)) {
                return true;
            }
            $error_message = $status->get_error_message();
            return false;
        }

        if (empty($status['connected'])) {
            $error_message = 'Google Search Console / Analytics is not connected. Connect Google in Integrations.';
            return false;
        }

        return true;
    }

    private function aysa_validate_test_access_key($provided_key = '', $site_host = '') {
        $provided_key = trim((string) $provided_key);
        $valid_keys = $this->aysa_get_test_access_keys();
        if ($provided_key === '') {
            return array(
                'valid' => false,
                'message' => 'Access key is required.',
                'code' => 'access_key_required',
            );
        }

        $site_host = trim((string) $site_host);
        if ($site_host === '') {
            $site_host = (string) wp_parse_url(home_url(), PHP_URL_HOST);
        }
        $site_host = strtolower(preg_replace('/^www\./', '', $site_host));

        if (
            $this->aysa_setup_is_local_host($site_host)
            && ($provided_key === 'local-dev-key' || $provided_key === 'AYSA-LOCAL-DEV-KEY-2026')
        ) {
            return array(
                'valid' => true,
                'message' => 'Access key accepted in local mode.',
                'code' => 'access_key_local_dev_master',
                'host' => $site_host,
                'account_id' => 'local_dev',
                'account_label' => 'local_dev',
                'key_id' => 'local-dev-master',
                'plan_code' => 'local_dev',
                'plan_price_eur' => 0,
                'source' => 'local_dev_master',
            );
        }

        // QA hardcoded keys remain supported for local testing.
        if (isset($valid_keys[$provided_key])) {
            $key_meta = $valid_keys[$provided_key];
            return array(
                'valid' => true,
                'message' => 'Access key is valid.',
                'host' => $site_host,
                'account_id' => (string) $key_meta['account_id'],
                'account_label' => (string) $key_meta['account_label'],
                'key_id' => (string) $key_meta['key_id'],
                'plan_code' => (string) $key_meta['plan_code'],
                'plan_price_eur' => intval($key_meta['plan_price_eur']),
                'source' => 'qa_static',
            );
        }

        // Real SaaS-issued keys must be validated by API source-of-truth.
        $api_validation = $this->aysa_validate_access_key_via_api($provided_key, $site_host);
        if (!empty($api_validation['valid'])) {
            return $api_validation;
        }

        return array(
            'valid' => false,
            'message' => (string) ($api_validation['message'] ?? 'Invalid access key.'),
            'code' => (string) ($api_validation['code'] ?? 'access_key_invalid'),
            'host' => $site_host,
        );
    }

    private function aysa_validate_access_key_via_api($provided_key = '', $site_host = '') {
        $provided_key = trim((string) $provided_key);
        if ($provided_key === '') {
            return array(
                'valid' => false,
                'message' => 'Access key is required.',
                'code' => 'access_key_required',
            );
        }

        if (!class_exists('Aysa_API_Config')) {
            require_once AYSA_PLUGIN_DIR . 'includes/config/aysa-api-config.php';
        }
        $base_url = class_exists('Aysa_API_Config') ? (string) Aysa_API_Config::get_base_url() : 'https://api.aysa.ai/';
        $api_url = rtrim($base_url, '/') . '/api/License/verify';

        $request_args = array(
            'timeout' => 25,
            'headers' => array(
                'Content-Type' => 'application/json',
                'Accept' => 'application/json',
            ),
            'body' => wp_json_encode(array(
                // Keep both legacy and current keys for compatibility with API versions.
                'access_key' => $provided_key,
                'api_key' => $provided_key,
                'domain' => $site_host,
                'site_host' => $site_host,
            )),
        );
        $response = wp_remote_post($api_url, $request_args);
        if (is_wp_error($response)) {
            return array(
                'valid' => false,
                'message' => 'Key verification service unavailable: ' . $response->get_error_message(),
                'code' => 'access_key_verify_unavailable',
            );
        }

        $code = (int) wp_remote_retrieve_response_code($response);
        $body = wp_remote_retrieve_body($response);
        $data = json_decode((string) $body, true);
        if (!is_array($data)) {
            return array(
                'valid' => false,
                'message' => 'Key verification returned invalid JSON.',
                'code' => 'access_key_verify_invalid_json',
            );
        }

        if ($code >= 400 || empty($data['ok'])) {
            return array(
                'valid' => false,
                'message' => (string) ($data['message'] ?? $data['error'] ?? ('Key verification failed (HTTP ' . $code . ').')),
                'code' => (string) ($data['code'] ?? 'access_key_verify_failed'),
            );
        }

        if (empty($data['valid'])) {
            return array(
                'valid' => false,
                'message' => (string) ($data['message'] ?? 'Invalid access key.'),
                'code' => (string) ($data['code'] ?? 'access_key_invalid'),
                'host' => (string) ($data['host'] ?? $site_host),
                'domain' => (string) ($data['domain'] ?? ''),
            );
        }

        return array(
            'valid' => true,
            'message' => (string) ($data['message'] ?? 'Access key is valid.'),
            'code' => (string) ($data['code'] ?? 'access_key_valid'),
            'host' => (string) ($data['host'] ?? $site_host),
            'domain' => (string) ($data['domain'] ?? ''),
            'key_id' => (string) ($data['key_id'] ?? ''),
            'key_label' => (string) ($data['key_label'] ?? 'primary'),
            'plan_code' => (string) ($data['plan_code'] ?? ''),
            'project_id' => intval($data['project_id'] ?? 0),
            'website_id' => intval($data['website_id'] ?? 0),
            'projection_sync_secret' => (string) ($data['projection_sync_secret'] ?? ''),
            'projection_sync_secret_version' => (string) ($data['projection_sync_secret_version'] ?? ''),
            'projection_sync_secret_scope' => (string) ($data['projection_sync_secret_scope'] ?? ''),
            'source' => (string) ($data['source'] ?? 'api'),
        );
    }

    public function handle_access_key_get_test() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }

        if (!$this->aysa_qa_controls_enabled()) {
            wp_send_json_error(array('message' => 'QA controls are unavailable.'), 404);
            return;
        }

        $plan_code = isset($_POST['plan_code']) ? sanitize_key(wp_unslash($_POST['plan_code'])) : 'starter_29';
        $key_map = $this->aysa_get_test_access_keys();
        $key = '';
        foreach ($key_map as $candidate => $meta) {
            if (($meta['plan_code'] ?? '') === $plan_code) {
                $key = $candidate;
                break;
            }
        }
        if ($key === '') {
            wp_send_json_error(array('message' => 'No local QA key is configured.'), 404);
            return;
        }
        $validation = $this->aysa_validate_test_access_key($key, isset($_POST['site_host']) ? sanitize_text_field(wp_unslash($_POST['site_host'])) : '');

        $available_test_keys = array();
        foreach ($key_map as $candidate => $meta) {
            $available_test_keys[] = array(
                'plan_code' => (string) ($meta['plan_code'] ?? ''),
                'price_eur' => intval($meta['plan_price_eur'] ?? 0),
                'key' => (string) $candidate,
            );
        }

        wp_send_json_success(array(
            'access_key' => $key,
            'key_id' => (string) ($validation['key_id'] ?? ''),
            'validation' => $validation,
            'available_test_keys' => $available_test_keys,
            'note' => 'Local QA keys configured outside the plugin package.',
        ));
    }

    public function handle_access_key_verify() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }

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

        $validation = $this->aysa_validate_test_access_key(
            $access_key,
            $site_host
        );

        if (empty($validation['valid'])) {
            wp_send_json_error($validation);
            return;
        }

        update_option(
            $this->aysa_test_access_key_option_name(),
            $access_key,
            false
        );

        if (!empty($validation['project_id'])) {
            update_option('aysa_project_id', intval($validation['project_id']), false);
        }
        if (!empty($validation['website_id'])) {
            update_option('aysa_website_id', intval($validation['website_id']), false);
        }
        $this->aysa_store_projection_sync_secret_from_validation($validation);

        $auth_bootstrap = null;
        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }
        if (class_exists('Aysa_Auth_Manager')) {
            $auth_manager = Aysa_Auth_Manager::get_instance();
            $auth_bootstrap = $auth_manager->verify_and_store_secret_key($access_key, $site_host);

            if (is_array($auth_bootstrap) && !empty($auth_bootstrap['success'])) {
                update_option('aysa_user_secret_key', $access_key, false);
                update_option('aysa_user_id', intval($auth_bootstrap['aysa_user_id'] ?? 0), false);
                update_option('aysa_auth_domain', $site_host !== '' ? $site_host : wp_parse_url(home_url(), PHP_URL_HOST), false);
            }
        }

        // Set wallet default plan based on verified key.
        if (!empty($validation['plan_code'])) {
            update_option('aysa_wallet_default_plan', sanitize_key((string) $validation['plan_code']), false);
            if (class_exists('Aysa_Wallet_Manager')) {
                $project_id = intval(get_option('aysa_project_id', 0));
                if ($project_id > 0) {
                    Aysa_Wallet_Manager::get_instance()->apply_plan_to_project(
                        $project_id,
                        sanitize_key((string) $validation['plan_code']),
                        false
                    );
                }
            }
        }

        if (is_array($auth_bootstrap) && empty($auth_bootstrap['success'])) {
            $validation['auth_bootstrap_warning'] = (string) ($auth_bootstrap['error'] ?? 'Authentication bootstrap failed after access key verification.');
        }

        wp_send_json_success($validation);
    }

    public function handle_activation_conversation() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }

        $conversation_language = isset($_POST['conversation_language']) ? sanitize_key((string) wp_unslash($_POST['conversation_language'])) : 'en';
        if (!in_array($conversation_language, array('en', 'ro', 'bg'), true)) {
            $conversation_language = 'en';
        }

        $user_message = isset($_POST['user_message']) ? sanitize_textarea_field((string) wp_unslash($_POST['user_message'])) : '';
        $site_host = isset($_POST['site_host']) ? sanitize_text_field((string) wp_unslash($_POST['site_host'])) : '';

        $result = $this->aysa_broker_request('POST', '/v1/conversation/activation/message', array(
            'conversation_language' => $conversation_language,
            'user_message' => $user_message,
            'site_host' => $site_host,
            'activation_url' => 'https://app.aysa.ai/websites',
        ), 45);

        if (is_wp_error($result)) {
            wp_send_json_error(array('message' => $result->get_error_message()));
            return;
        }
        if (!is_array($result) || empty($result['ok'])) {
            wp_send_json_error(array(
                'message' => isset($result['error']) ? (string) $result['error'] : 'Activation conversation request failed.',
            ));
            return;
        }

        wp_send_json_success($result);
    }

    public function handle_agent8v3_message() {
        $financial_approval = array();
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- The delegated canonical proxy verifies the same AJAX nonce before the broker request.
        if (isset($_POST['financial_approval'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Structured approval is validated field-by-field and fail-closed by the canonical API service.
            $decoded_approval = json_decode((string) wp_unslash($_POST['financial_approval']), true);
            if (is_array($decoded_approval)) {
                $financial_approval = $decoded_approval;
            }
        }
        $this->handle_agent8v3_proxy('POST', '/v1/agent8v3/message', array(
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'message' => isset($_POST['message']) ? sanitize_textarea_field((string) wp_unslash($_POST['message'])) : '',
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'state_version' => isset($_POST['state_version']) ? intval((string) wp_unslash($_POST['state_version'])) : 0,
            'financial_approval' => $financial_approval,
            'compact_response' => true,
        ));
    }

    public function handle_publish_generated_article_to_post() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }

        $title = sanitize_text_field((string) wp_unslash($_POST['title'] ?? ''));
        $body_html = wp_kses_post((string) wp_unslash($_POST['body_html'] ?? ''));
        if ($title === '' || trim(wp_strip_all_tags($body_html)) === '') {
            wp_send_json_error(array('message' => 'Missing article title or body.'));
            return;
        }

        $post_id = wp_insert_post(array(
            'post_title' => $title,
            'post_content' => $body_html,
            'post_excerpt' => wp_trim_words(wp_strip_all_tags($body_html), 35, '...'),
            'post_status' => 'publish',
            'post_type' => 'post',
            'post_author' => get_current_user_id(),
        ), true);

        if (is_wp_error($post_id)) {
            wp_send_json_error(array('message' => $post_id->get_error_message()));
            return;
        }

        $post_id = intval($post_id);
        $primary_keyword = sanitize_text_field((string) wp_unslash($_POST['primary_keyword'] ?? ''));
        $secondary_keywords = sanitize_text_field((string) wp_unslash($_POST['secondary_keywords'] ?? ''));
        if ($primary_keyword !== '') {
            update_post_meta($post_id, 'aysa_main_keyword', $primary_keyword);
        }
        if ($secondary_keywords !== '') {
            update_post_meta($post_id, 'aysa_secondary_keywords', $secondary_keywords);
        }
        update_post_meta($post_id, 'aysa_generation_source', 'aysa-plugin-generated-article');
        update_post_meta($post_id, '_aysa_generated_content', array(
            'generated_at' => current_time('mysql'),
            'source' => 'plugin-generated-article-publish',
            'primary_keyword' => $primary_keyword,
            'secondary_keywords' => $secondary_keywords,
            'external_draft_id' => intval(wp_unslash($_POST['external_draft_id'] ?? 0)),
        ));

        wp_send_json_success(array(
            'post_id' => $post_id,
            'edit_url' => get_edit_post_link($post_id, 'raw'),
            'view_url' => get_permalink($post_id),
        ));
    }

    public function handle_agent8v3_action() {
        $action_payload = array();
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        if (isset($_POST['payload'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
            $decoded_payload = json_decode((string) wp_unslash($_POST['payload']), true);
            if (is_array($decoded_payload)) {
                $action_payload = $decoded_payload;
            }
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $agent_action = isset($_POST['agent_action']) ? sanitize_key((string) wp_unslash($_POST['agent_action'])) : '';
        if (
            $agent_action === 'execute_technical_fix_plan'
            && sanitize_key((string) ($action_payload['module'] ?? '')) === 'internal_linking'
        ) {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            if (empty($action_payload['accepted_fixes']) && is_array($action_payload['selected_items'] ?? null)) {
                $action_payload['accepted_fixes'] = array_values($action_payload['selected_items']);
            }
            $product_gates = $this->aysa_agent8v3_internal_linking_product_gates();
            $action_payload['technical_internal_linking_snapshot'] = $this->aysa_adjustments_get_technical_internal_linking_snapshot(
                $project_id
            );
            $action_payload['technical_site_audit'] = $this->aysa_agent8_transport_reduce_technical_site_audit_snapshot(
                $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id)
            );
            $action_payload['product_gates'] = $product_gates;
            $action_payload['internal_linking_project_gate_enabled'] = !empty($product_gates['project_gate_enabled']);
            $action_payload['internal_linking_site_gate_enabled'] = !empty($product_gates['site_gate_enabled']);
        }
        if (in_array($agent_action, array('start_keyword_research_run', 'retry_keyword_research_run'), true)) {
            // Build this inventory on the trusted WordPress side. The browser
            // must not decide which products, pages or taxonomies exist.
            $action_payload['page_inventory'] = $this->aysa_keyword_mapping_collect_site_page_inventory();
        }
        if (
            $agent_action === 'execute_technical_fix_plan'
            && sanitize_key((string) ($action_payload['module'] ?? '')) === 'image_alt'
        ) {
            $product_gates = $this->aysa_agent8v3_image_alt_product_gates();
            $action_payload['technical_site_audit'] = $this->aysa_agent8_transport_reduce_technical_site_audit_snapshot(
                $this->aysa_adjustments_get_technical_site_audit_snapshot(
                    $this->aysa_resolve_project_id_with_local_fallback(0)
                )
            );
            $action_payload['product_gates'] = $product_gates;
            $action_payload['image_alt_project_gate_enabled'] = !empty($product_gates['project_gate_enabled']);
            $action_payload['image_alt_site_gate_enabled'] = !empty($product_gates['site_gate_enabled']);
        }
        if (
            $agent_action === 'execute_technical_fix_plan'
            && sanitize_key((string) ($action_payload['module'] ?? '')) === 'metadata'
        ) {
            $product_gates = $this->aysa_agent8v3_metadata_product_gates();
            $action_payload['technical_site_audit'] = $this->aysa_agent8_transport_reduce_technical_site_audit_snapshot(
                $this->aysa_adjustments_get_technical_site_audit_snapshot(
                    $this->aysa_resolve_project_id_with_local_fallback(0)
                )
            );
            $action_payload['product_gates'] = $product_gates;
            $action_payload['metadata_project_gate_enabled'] = !empty($product_gates['project_gate_enabled']);
            $action_payload['metadata_site_gate_enabled'] = !empty($product_gates['site_gate_enabled']);
        }
        if (
            $agent_action === 'execute_technical_fix_plan'
            && sanitize_key((string) ($action_payload['module'] ?? '')) === 'canonical'
        ) {
            if (
                empty($action_payload['selected_items'])
                && !empty($action_payload['post_id'])
                && !empty($action_payload['url'])
                && (!empty($action_payload['proposed_canonical']) || !empty($action_payload['proposed_value']))
            ) {
                $proposed_canonical = (string) ($action_payload['proposed_canonical'] ?? $action_payload['proposed_value'] ?? '');
                $current_canonical = (string) ($action_payload['current_canonical'] ?? $action_payload['old_value'] ?? '');
                $action_payload['selected_items'] = array(array(
                    'row_id' => sanitize_text_field((string) ($action_payload['row_id'] ?? $action_payload['item_id'] ?? '')),
                    'payload_hash' => sanitize_text_field((string) ($action_payload['payload_hash'] ?? '')),
                    'post_id' => intval($action_payload['post_id'] ?? 0),
                    'url' => esc_url_raw((string) ($action_payload['url'] ?? '')),
                    'provider' => sanitize_key((string) ($action_payload['provider'] ?? 'unknown')),
                    'reason' => sanitize_key((string) ($action_payload['reason'] ?? 'missing_self_canonical')),
                    'current_canonical' => esc_url_raw($current_canonical),
                    'old_value' => esc_url_raw((string) ($action_payload['old_value'] ?? $current_canonical)),
                    'old_value_captured' => array_key_exists('old_value', $action_payload) || array_key_exists('current_canonical', $action_payload),
                    'proposed_canonical' => esc_url_raw($proposed_canonical),
                    'proposed_value' => esc_url_raw($proposed_canonical),
                    'accepted_by_user' => true,
                ));
            }
            if (empty($action_payload['accepted_fixes']) && is_array($action_payload['selected_items'] ?? null)) {
                $action_payload['accepted_fixes'] = array_values($action_payload['selected_items']);
            }
            $product_gates = $this->aysa_agent8v3_canonical_product_gates();
            $action_payload['technical_site_audit'] = $this->aysa_agent8_transport_reduce_technical_site_audit_snapshot(
                $this->aysa_adjustments_get_technical_site_audit_snapshot(
                    $this->aysa_resolve_project_id_with_local_fallback(0)
                )
            );
            $action_payload['product_gates'] = $product_gates;
            $action_payload['canonical_project_gate_enabled'] = !empty($product_gates['project_gate_enabled']);
            $action_payload['canonical_site_gate_enabled'] = !empty($product_gates['site_gate_enabled']);
        }
        if (
            $agent_action === 'execute_technical_fix_plan'
            && sanitize_key((string) ($action_payload['module'] ?? '')) === 'schema'
        ) {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            $technical_suite_auto_schema = !empty($action_payload['technical_seo_suite_auto_schema']);
            if (
                empty($action_payload['selected_items'])
                && empty($action_payload['accepted_fixes'])
                && $technical_suite_auto_schema
            ) {
                $max_items = max(1, min(3, intval($action_payload['schema_auto_apply_max_items'] ?? 3)));
                $selected_items = $this->aysa_agent8v3_build_schema_controlled_candidates($max_items);
                $rejected_ids = array();
                if (isset($action_payload['rejected_schema_row_ids']) && is_array($action_payload['rejected_schema_row_ids'])) {
                    foreach ($action_payload['rejected_schema_row_ids'] as $rejected_id) {
                        $rejected_ids[] = sanitize_text_field((string) $rejected_id);
                    }
                }
                if ($rejected_ids !== array()) {
                    $selected_items = array_values(array_filter($selected_items, static function ($item) use ($rejected_ids) {
                        if (!is_array($item)) {
                            return false;
                        }
                        $row_id = sanitize_text_field((string) ($item['row_id'] ?? $item['item_id'] ?? ''));
                        return $row_id === '' || !in_array($row_id, $rejected_ids, true);
                    }));
                }
                foreach ($selected_items as $index => $item) {
                    if (is_array($item)) {
                        $item['accepted_by_user'] = true;
                        $item['accepted_by_default'] = true;
                        $item['acceptance_model'] = 'technical_seo_accept_all_unless_rejected';
                        $selected_items[$index] = $item;
                    }
                }
                if ($selected_items !== array()) {
                    $action_payload['selected_items'] = $selected_items;
                    $action_payload['accepted_fixes'] = $selected_items;
                    update_option($this->aysa_agent8v3_schema_controlled_candidates_option_key(), $selected_items, false);
                }
            }
            if (empty($action_payload['accepted_fixes']) && is_array($action_payload['selected_items'] ?? null)) {
                $action_payload['accepted_fixes'] = array_values($action_payload['selected_items']);
            }
            $product_gates = $this->aysa_agent8v3_schema_product_gates();
            $snapshot = $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id);
            $schema_preview_rows = $this->aysa_agent8v3_schema_controlled_candidates_rows();
            if ($schema_preview_rows !== array()) {
                $snapshot['schema_preview'] = array(
                    'source' => 'plugin_controlled_schema_candidates',
                    'rows' => $schema_preview_rows,
                );
            }
            $action_payload['technical_site_audit'] = $this->aysa_agent8_transport_reduce_technical_site_audit_snapshot($snapshot);
            $action_payload['schema_preview'] = array(
                'source' => 'plugin_controlled_schema_candidates',
                'rows' => $schema_preview_rows,
            );
            $action_payload['product_gates'] = $product_gates;
            $action_payload['schema_project_gate_enabled'] = !empty($product_gates['project_gate_enabled']);
            $action_payload['schema_site_gate_enabled'] = !empty($product_gates['site_gate_enabled']);
            $site_host = wp_parse_url(home_url('/'), PHP_URL_HOST);
            $site_host = is_string($site_host) ? strtolower(trim($site_host)) : '';
            $action_payload['project_id'] = intval($action_payload['project_id'] ?? $project_id);
            $action_payload['website_id'] = intval($action_payload['website_id'] ?? $project_id);
            if ($site_host !== '') {
                $action_payload['site_host'] = sanitize_text_field((string) ($action_payload['site_host'] ?? $site_host));
                $action_payload['domain'] = sanitize_text_field((string) ($action_payload['domain'] ?? $site_host));
                $action_payload['site_identity'] = sanitize_text_field((string) ($action_payload['site_identity'] ?? ('site:' . $site_host)));
            }
        }
        if (
            $agent_action === 'execute_technical_fix_plan'
            && sanitize_key((string) ($action_payload['module'] ?? '')) === 'image_optimization'
        ) {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            if (empty($action_payload['accepted_fixes']) && is_array($action_payload['selected_items'] ?? null)) {
                $action_payload['accepted_fixes'] = array_values($action_payload['selected_items']);
            }
            if (is_array($action_payload['selected_items'] ?? null)) {
                $selected_attachment_ids = array();
                foreach ($action_payload['selected_items'] as $selected_item) {
                    if (!is_array($selected_item)) {
                        continue;
                    }
                    $attachment_id = intval($selected_item['attachment_id'] ?? $selected_item['media_id'] ?? 0);
                    if ($attachment_id > 0) {
                        $selected_attachment_ids[] = $attachment_id;
                    }
                }
                if (empty($action_payload['selected_attachment_ids']) && $selected_attachment_ids !== array()) {
                    $action_payload['selected_attachment_ids'] = array_values(array_unique($selected_attachment_ids));
                }
                $selected_count = count($action_payload['selected_items']);
                if (empty($action_payload['accepted_count'])) {
                    $action_payload['accepted_count'] = $selected_count;
                }
                if (empty($action_payload['accepted_fix_count'])) {
                    $action_payload['accepted_fix_count'] = $selected_count;
                }
            }
            $product_gates = $this->aysa_agent8v3_image_optimization_product_gates();
            $snapshot = $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id);
            $image_optimization_rows = $this->aysa_agent8v3_image_optimization_crawl_scoped_candidates_rows($project_id);
            if ($image_optimization_rows !== array()) {
                $snapshot['image_optimization_preview'] = array(
                    'source' => 'plugin_crawl_scoped_image_optimization_candidates',
                    'rows' => $image_optimization_rows,
                );
            }
            $action_payload['technical_site_audit'] = $this->aysa_agent8_transport_reduce_technical_site_audit_snapshot($snapshot);
            $action_payload['image_optimization_preview'] = array(
                'source' => 'plugin_crawl_scoped_image_optimization_candidates',
                'rows' => $image_optimization_rows,
            );
            $action_payload['product_gates'] = $product_gates;
            $action_payload['image_optimization_project_gate_enabled'] = !empty($product_gates['project_gate_enabled']);
            $action_payload['image_optimization_site_gate_enabled'] = !empty($product_gates['site_gate_enabled']);
        }
        if (
            $agent_action === 'execute_technical_fix_plan'
            && sanitize_key((string) ($action_payload['module'] ?? '')) === 'redirects'
        ) {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            if (empty($action_payload['accepted_fixes']) && is_array($action_payload['selected_items'] ?? null)) {
                $action_payload['accepted_fixes'] = array_values($action_payload['selected_items']);
            }
            $product_gates = $this->aysa_agent8v3_redirect_product_gates();
            $action_payload['technical_site_audit'] = $this->aysa_agent8_transport_reduce_technical_site_audit_snapshot(
                $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id)
            );
            $action_payload['product_gates'] = $product_gates;
            $action_payload['redirect_project_gate_enabled'] = !empty($product_gates['project_gate_enabled']);
            $action_payload['redirect_site_gate_enabled'] = !empty($product_gates['site_gate_enabled']);
            $action_payload['redirect_runtime_provider_enabled'] = !empty($product_gates['runtime_provider_enabled']);
        }
        if ($agent_action === 'prepare_image_alt_text') {
            $action_payload['website'] = home_url('/');
            $action_payload['target_url'] = home_url('/');
            $action_payload['plugin_projection_target_url'] = home_url('/');
            $action_payload['technical_site_audit'] = $this->aysa_agent8_transport_reduce_technical_site_audit_snapshot(
                $this->aysa_adjustments_get_technical_site_audit_snapshot(
                    $this->aysa_resolve_project_id_with_local_fallback(0)
                )
            );
        }
        if ($agent_action === 'generate_pagespeed_report') {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            $action_payload['technical_pagespeed_snapshot'] = $this->aysa_adjustments_get_technical_pagespeed_snapshot($project_id);
        }
        if (in_array($agent_action, array('generate_onpage_meta', 'generate_onpage_rewrite_decision', 'generate_onpage_content_rewrite'), true)) {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            if (
                $agent_action === 'generate_onpage_content_rewrite'
                && sanitize_key((string) ($action_payload['confirmation_kind'] ?? '')) === 'onpage_aeo_preview_generation'
            ) {
                $aeo_preview_error = $this->aysa_agent8_validate_onpage_aeo_preview_generation_payload($action_payload);
                if ($aeo_preview_error !== '') {
                    wp_send_json_error(array(
                        'code' => $aeo_preview_error,
                        'message' => $this->aysa_agent8_onpage_aeo_preview_generation_error_message($aeo_preview_error),
                    ), 400);
                    return;
                }
            }
            $action_payload = $this->aysa_agent8_prepare_onpage_meta_generation_payload($action_payload, $project_id);
            if (in_array(
                sanitize_key((string) ($action_payload['confirmation_kind'] ?? '')),
                array('onpage_content_evaluation_generation', 'onpage_content_rewrite_generation'),
                true
            )) {
                $content_preflight_error = $this->aysa_agent8_validate_onpage_content_generation_payload($action_payload, $agent_action);
                if ($content_preflight_error !== '') {
                    wp_send_json_error(array(
                        'code' => $content_preflight_error,
                        'message' => $this->aysa_agent8_onpage_content_generation_error_message($content_preflight_error),
                    ), 400);
                    return;
                }
            }
        }
        if (
            $agent_action === 'execute_onpage_plan'
            && sanitize_key((string) ($action_payload['section'] ?? '')) === 'content'
            && sanitize_key((string) ($action_payload['operation'] ?? '')) === 'apply_rewrite'
        ) {
            $action_payload['product_gates'] = $this->aysa_agent8v3_onpage_content_rewrite_product_gates();
            $action_payload['content_rewrite_project_gate_enabled'] = !empty($action_payload['product_gates']['project_gate_enabled']);
            $action_payload['content_rewrite_site_gate_enabled'] = !empty($action_payload['product_gates']['site_gate_enabled']);
        }
        if ($agent_action === 'generate_onpage_keyword_mapping') {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            $action_payload = $this->aysa_agent8_prepare_onpage_keyword_mapping_payload($action_payload, $project_id);
        }
        if ($agent_action === 'continue_onpage_agent') {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            $decision = $this->aysa_agent8_resolve_onpage_agent_decision($project_id);
            if ($decision !== array()) {
                $action_payload['onpage_agent_decision'] = $decision;
                $action_payload['agent_next_step'] = sanitize_key((string) ($decision['next_step'] ?? ''));
                if (isset($decision['selection']) && is_array($decision['selection']) && $decision['selection'] !== array() && empty($action_payload['selection'])) {
                    $action_payload['selection'] = array_values(array_filter(array_map('strval', $decision['selection'])));
                }
            }
            $agent_next_step = sanitize_key((string) ($action_payload['agent_next_step'] ?? ''));
            if ($agent_next_step === 'keyword_mapping') {
                $action_payload = $this->aysa_agent8_prepare_onpage_keyword_mapping_payload($action_payload, $project_id);
            } elseif ($agent_next_step === 'meta_apply') {
                $action_payload['section'] = 'meta_snippets';
                $action_payload['operation'] = 'apply_meta';
                $action_payload['write_class'] = 'onpage_metadata_apply';
                $action_payload['auto_apply_safe'] = true;
                $action_payload = $this->aysa_agent8_prepare_onpage_meta_apply_payload($action_payload, $project_id);
            } elseif ($agent_next_step === 'aeo_apply') {
                $action_payload['section'] = 'aeo_content_qa';
                $action_payload['operation'] = 'apply_rewrite';
                $action_payload['write_class'] = 'onpage_aeo_appendix_apply';
                $action_payload['auto_apply_safe'] = false;
                $action_payload['writes_enabled'] = false;
                $action_payload['billing_enabled'] = false;
                $action_payload['_aysa_onpage_apply_error'] = 'onpage_aeo_appendix_contract_needed';
            } elseif (in_array($agent_next_step, array('meta_generate', 'rewrite_decide', 'rewrite_generate', 'aeo_generate', 'image_alt', 'pipeline'), true)) {
                $action_payload = $this->aysa_agent8_prepare_onpage_meta_generation_payload($action_payload, $project_id);
            }
        }
        if ($agent_action === 'revert_onpage_item') {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            $service = $this->aysa_onpage_workspace_service();
            if (!$service || !method_exists($service, 'revert_onpage_item')) {
                wp_send_json_error(array(
                    'code' => 'onpage_revert_unavailable',
                    'message' => 'Revert On Page nu este disponibil.',
                ), 400);
                return;
            }
            $entity_key = sanitize_text_field((string) ($action_payload['entity_key'] ?? $action_payload['item_id'] ?? ''));
            $operation = sanitize_key((string) ($action_payload['operation'] ?? 'apply_meta'));
            $action_payload['onpage_revert_result'] = $service->revert_onpage_item($project_id, $entity_key, $operation);
        }
        if (in_array($agent_action, array('configure_monitoring_policy', 'prepare_monitoring_serp_refresh_preflight', 'refresh_monitoring_rank_tracking', 'refresh_monitoring_rank_tracking_status', 'settle_monitoring_rank_tracking_over_cap', 'refresh_monitoring_ai_visibility', 'refresh_monitoring_ai_visibility_status', 'show_monitoring_overview'), true)) {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            $context = $this->aysa_monitoring_resolve_context('');
            $monitoring_bundle = $this->aysa_agent8_transport_reduce_bundle(array(
                'monitoring_rank_tracking' => $this->aysa_agent8_get_monitoring_rank_tracking_snapshot($project_id),
                'monitoring_ai_overview' => $this->aysa_agent8_get_monitoring_ai_overview_snapshot($project_id),
                'monitoring_query_packs' => $this->aysa_agent8_get_monitoring_query_packs_snapshot($project_id),
            ));
            $action_payload['project_id'] = intval($project_id);
            $action_payload['monitoring_run_status'] = $this->aysa_agent8v3_sync_monitoring_worker_runs($project_id);
            $action_payload['monitoring_rank_tracking'] = isset($monitoring_bundle['monitoring_rank_tracking']) && is_array($monitoring_bundle['monitoring_rank_tracking']) ? $monitoring_bundle['monitoring_rank_tracking'] : array();
            $action_payload['monitoring_ai_overview'] = isset($monitoring_bundle['monitoring_ai_overview']) && is_array($monitoring_bundle['monitoring_ai_overview']) ? $monitoring_bundle['monitoring_ai_overview'] : array();
            $action_payload['monitoring_query_packs'] = isset($monitoring_bundle['monitoring_query_packs']) && is_array($monitoring_bundle['monitoring_query_packs']) ? $monitoring_bundle['monitoring_query_packs'] : array();
            $action_payload['canonical_project_id'] = intval($project_id);
            $action_payload['target'] = (string) ($context['target'] ?? '');
            $action_payload['site_host'] = (string) ($context['target'] ?? '');
            $action_payload['se_domain'] = (string) ($context['se_domain'] ?? 'google.com');
            $action_payload['location_code'] = intval($context['location_code'] ?? 2840);
            $action_payload['language_code'] = strtolower((string) ($context['language_code'] ?? 'en'));
            $action_payload['monitoring_source_configuration'] = array(
                'se_domain' => $action_payload['se_domain'],
                'location_code' => $action_payload['location_code'],
                'language_code' => $action_payload['language_code'],
                'device' => 'desktop',
            );
            if ($agent_action === 'settle_monitoring_rank_tracking_over_cap') {
                $rank_status = isset($action_payload['monitoring_run_status']['rank']) && is_array($action_payload['monitoring_run_status']['rank'])
                    ? $action_payload['monitoring_run_status']['rank']
                    : array();
                $rank_run = isset($rank_status['run']) && is_array($rank_status['run']) ? $rank_status['run'] : array();
                $billing_state = isset($rank_status['billing_state']) && is_array($rank_status['billing_state']) ? $rank_status['billing_state'] : array();
                $computed_actual = max(0, intval($billing_state['computed_actual_cents'] ?? 0));
                $action_payload['run_id'] = intval($rank_run['id'] ?? 0);
                $action_payload['confirmation_kind'] = 'monitoring_over_cap_reapproval';
                $action_payload['explicit_confirmation'] = true;
                $action_payload['approved_actual_credits_cents'] = $computed_actual;
                $action_payload['max_total_cost'] = $computed_actual;
            }
            if ($agent_action === 'refresh_monitoring_rank_tracking') {
                $action_payload['run_type'] = 'monitoring_rank_tracking_refresh';
                $action_payload['associations'] = $this->aysa_agent8_get_monitoring_associations($project_id);
                $approved_scope_count = count($action_payload['associations']);
                $action_payload['max_keywords'] = max(1, intval($action_payload['max_keywords'] ?? $approved_scope_count));
                if ($approved_scope_count > 0) {
                    $action_payload['max_keywords'] = min($action_payload['max_keywords'], $approved_scope_count);
                }
                $action_payload['force'] = !empty($action_payload['force']);
            }
            if ($agent_action === 'refresh_monitoring_ai_visibility') {
                $ai_payload = $this->aysa_monitoring_build_ai_payload();
                if (isset($action_payload['query_pack']) && is_array($action_payload['query_pack'])) {
                    $ai_payload['query_pack'] = array_values($action_payload['query_pack']);
                }
                $action_payload = array_merge($action_payload, $ai_payload, array('run_type' => 'monitoring_ai_visibility_refresh'));
            }
        }
        if ($agent_action === 'prepare_monitoring_serp_refresh_preflight') {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            $context = $this->aysa_monitoring_resolve_context('');
            $action_payload['project_id'] = intval($project_id);
            $action_payload['canonical_project_id'] = intval($project_id);
            $action_payload['run_type'] = 'monitoring_rank_tracking_refresh';
            $action_payload['target'] = (string) ($context['target'] ?? '');
            $action_payload['site_host'] = (string) ($context['target'] ?? '');
            $action_payload['se_domain'] = (string) ($context['se_domain'] ?? 'google.com');
            $action_payload['location_code'] = intval($context['location_code'] ?? 2840);
            $action_payload['language_code'] = strtolower((string) ($context['language_code'] ?? 'en'));
            $action_payload['monitoring_source_configuration'] = array(
                'se_domain' => $action_payload['se_domain'],
                'location_code' => $action_payload['location_code'],
                'language_code' => $action_payload['language_code'],
                'device' => 'desktop',
            );
        }
        if (in_array($agent_action, array(
            'show_offpage_google_business_profile',
            'set_offpage_gbp_primary_location',
            'refresh_offpage_gbp_reviews',
            'refresh_offpage_gbp_performance',
            'create_offpage_gbp_post',
            'reply_offpage_gbp_review',
            'upload_offpage_gbp_media',
            'update_offpage_gbp_business_info',
            'update_offpage_gbp_hours',
            'update_offpage_gbp_attributes',
            'update_offpage_gbp_services',
        ), true)) {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            $action_payload['offpage_gbp_overview'] = $this->aysa_agent8_get_offpage_gbp_overview_snapshot($project_id, true);
        }
        if (in_array($agent_action, array(
            'show_offpage_gbp_social_calendar',
            'generate_offpage_gbp_social_calendar',
            'refresh_offpage_gbp_social_calendar_status',
            'reject_offpage_gbp_social_post',
            'retry_offpage_gbp_social_post',
        ), true)) {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            $action_payload['offpage_gbp_social_calendar'] = $this->aysa_agent8_get_gbp_social_calendar_snapshot($project_id);
            if ($agent_action === 'generate_offpage_gbp_social_calendar') {
                $action_payload['offpage_gbp_social_sources'] = $this->aysa_agent8_get_gbp_social_sources($project_id);
            }
        }
        if (in_array($agent_action, array(
            'continue_offpage_agent',
            'show_offpage_link_exchange',
            'prepare_offpage_link_exchange_request_preflight',
            'find_offpage_link_exchange_matches',
            'request_offpage_link_exchange',
            'set_offpage_link_exchange_autoactivation',
            'simulate_accept_offpage_exchange_request',
            'insert_offpage_exchange_partner_box',
            'verify_offpage_exchange_request',
            'pause_offpage_exchange_request',
            'show_offpage_backlink_audit',
            'run_offpage_backlink_audit',
            'refresh_offpage_backlink_audit_status',
            'generate_offpage_backlink_strategy',
            'refresh_offpage_backlink_strategy_status',
                'generate_offpage_adverlink_article',
                'refresh_offpage_adverlink_article_status',
                'publish_offpage_adverlink_article',
                'queue_offpage_publisher_article_order',
                'preview_offpage_link_building_cart',
            'approve_offpage_link_building_cart',
        ), true)) {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            $action_payload['offpage_backlink_audit'] = $this->aysa_agent8_get_offpage_backlink_audit_snapshot($project_id);
            $action_payload['offpage_backlink_strategy'] = $agent_action === 'generate_offpage_backlink_strategy'
                ? array()
                : $this->aysa_agent8_get_offpage_backlink_strategy_snapshot($project_id);
            $action_payload['offpage_adverlink_orders'] = $this->aysa_agent8_compact_adverlink_orders(
                $this->aysa_agent8_get_offpage_orders_snapshot($project_id)
            );
            $action_payload['offpage_link_exchange_profile'] = $this->aysa_agent8_get_link_exchange_profile_for_agent8($project_id);
            if ($agent_action === 'prepare_offpage_link_exchange_request_preflight') {
                $action_payload['partner_query'] = sanitize_text_field((string) ($action_payload['partner_query'] ?? $action_payload['user_message'] ?? ''));
                $action_payload['offpage_link_exchange_requests'] = $this->aysa_agent8_get_link_exchange_cached_requests_snapshot($project_id);
            }
            if ($agent_action === 'show_offpage_link_exchange') {
                $action_payload['offpage_link_exchange_requests'] = $this->aysa_agent8_get_link_exchange_requests_snapshot($project_id);
            }
            if (in_array($agent_action, array(
                'find_offpage_link_exchange_matches',
                'request_offpage_link_exchange',
                'set_offpage_link_exchange_autoactivation',
                'simulate_accept_offpage_exchange_request',
                'insert_offpage_exchange_partner_box',
                'verify_offpage_exchange_request',
                'pause_offpage_exchange_request',
            ), true)) {
                $action_payload['offpage_link_exchange_requests'] = $this->aysa_agent8_get_link_exchange_requests_snapshot($project_id);
            }
            $action_payload['research_snapshot'] = $this->aysa_agent8_compact_backlink_strategy_research_snapshot($project_id);
            if ($agent_action === 'generate_offpage_backlink_strategy') {
                $action_payload['link_building_targets'] = $this->aysa_agent8_get_link_building_targets($project_id);
            }
            if ($agent_action === 'generate_offpage_adverlink_article' || $agent_action === 'publish_offpage_adverlink_article' || $agent_action === 'queue_offpage_publisher_article_order') {
                $action_payload['homepage_link'] = $this->aysa_agent8_get_homepage_link_building_target($project_id);
                $action_payload['language'] = 'ro';
                $action_payload['min_words'] = 2000;
                if (!isset($action_payload['recommendation']) || !is_array($action_payload['recommendation'])) {
                    $action_payload['recommendation'] = $action_payload;
                }
            }
            $onpage_workspace = $this->aysa_agent8_get_onpage_workspace_snapshot($project_id);
            $onpage_rows = isset($onpage_workspace['rows']) && is_array($onpage_workspace['rows']) ? array_values($onpage_workspace['rows']) : array();
            $action_payload['onpage_workspace'] = array(
                'rows' => array_slice(array_map(static function ($row) {
                    $row = is_array($row) ? $row : array();
                    return array(
                        'url' => esc_url_raw((string) ($row['url'] ?? $row['permalink'] ?? $row['target_url'] ?? '')),
                        'title' => sanitize_text_field((string) ($row['title'] ?? $row['entity_title'] ?? '')),
                        'primary_keyword' => sanitize_text_field((string) ($row['primary_keyword'] ?? $row['keyword'] ?? '')),
                        'keyword' => sanitize_text_field((string) ($row['keyword'] ?? $row['primary_keyword'] ?? '')),
                        'entity_group' => sanitize_key((string) ($row['entity_group'] ?? '')),
                        'entity_type' => sanitize_key((string) ($row['entity_type'] ?? '')),
                    );
                }, $onpage_rows), 0, 100),
            );
        }
        $beta_block = $this->aysa_agent8v3_beta_action_block($agent_action, $action_payload);
        if ($beta_block !== array()) {
            wp_send_json_error(array(
                'code' => sanitize_key((string) ($beta_block['code'] ?? 'beta_action_disabled')),
                'message' => (string) ($beta_block['message'] ?? 'This action is disabled by controlled beta safety policy.'),
                'flag' => sanitize_key((string) ($beta_block['flag'] ?? '')),
                'release' => function_exists('aysa_release_metadata') ? aysa_release_metadata() : array(),
            ), 403);
            return;
        }
        if (
            $agent_action === 'execute_onpage_plan'
            && sanitize_key((string) ($action_payload['section'] ?? '')) === 'meta_snippets'
            && sanitize_key((string) ($action_payload['operation'] ?? '')) === 'apply_meta'
        ) {
            $accepted_bundle_confirmed = sanitize_key((string) ($action_payload['confirmation_kind'] ?? '')) === 'onpage_seo_apply_accepted_bundle'
                && !empty($action_payload['onpage_seo_apply_accepted_bundle'])
                && (!empty($action_payload['user_confirmed']) || !empty($action_payload['confirmed_by_user']));
            if (!$accepted_bundle_confirmed) {
                wp_send_json_error(array(
                    'code' => 'onpage_apply_requires_confirmation',
                    'message' => 'Aplicarea On Page necesită confirmare explicită înainte de a scrie în WordPress.',
                ), 400);
                return;
            }
            $approved_items = array_values(array_filter(
                is_array($action_payload['accepted_items'] ?? null) ? $action_payload['accepted_items'] : array(),
                'is_array'
            ));
            $approved_hash_items = array();
            $approved_entity_keys = array();
            $approved_provider_output_ids = array();
            foreach ($approved_items as $approved_item) {
                $entity_key = sanitize_text_field((string) ($approved_item['entity_key'] ?? $approved_item['item_id'] ?? ''));
                $provider_output_id = sanitize_text_field((string) ($approved_item['provider_output_id'] ?? ''));
                $proposal_hash = sanitize_text_field((string) ($approved_item['proposal_hash'] ?? ''));
                if ($entity_key === '' || $provider_output_id === '' || $proposal_hash === '') {
                    continue;
                }
                $approved_entity_keys[] = $entity_key;
                $approved_provider_output_ids[] = $provider_output_id;
                $approved_hash_items[] = array(
                    'item_id' => $entity_key,
                    'entity_key' => $entity_key,
                    'provider_output_id' => $provider_output_id,
                    'proposal_hash' => $proposal_hash,
                );
            }
            usort($approved_hash_items, static function($left, $right) {
                return strcmp((string) ($left['entity_key'] ?? ''), (string) ($right['entity_key'] ?? ''));
            });
            $approved_entity_keys = array_values(array_unique($approved_entity_keys));
            $approved_provider_output_ids = array_values(array_unique($approved_provider_output_ids));
            sort($approved_entity_keys, SORT_STRING);
            sort($approved_provider_output_ids, SORT_STRING);
            $approved_selection_count = count($approved_hash_items);
            $approved_unit_cents = max(1, intval($action_payload['billing_unit_cents'] ?? 5));
            $approved_max_total_cost_cents = intval($action_payload['max_total_cost_cents'] ?? $action_payload['max_cost_cents'] ?? 0);
            $required_max_total_cost_cents = $approved_selection_count * 2 * $approved_unit_cents;
            if (
                $approved_selection_count <= 0
                || $approved_selection_count !== count($approved_items)
                || count($approved_entity_keys) !== $approved_selection_count
                || count($approved_provider_output_ids) !== $approved_selection_count
                || $approved_max_total_cost_cents !== $required_max_total_cost_cents
            ) {
                wp_send_json_error(array(
                    'code' => 'onpage_approved_selection_contract_invalid',
                    'message' => 'Selecția sau plafonul confirmat s-a schimbat. Aplicarea a fost oprită înainte de orice scriere.',
                    'details' => array(
                        'selected_count' => $approved_selection_count,
                        'approved_max_total_cost_cents' => $approved_max_total_cost_cents,
                        'required_max_total_cost_cents' => $required_max_total_cost_cents,
                    ),
                ), 409);
                return;
            }
            $approved_selection_hash = hash('sha256', wp_json_encode($approved_hash_items));
            $action_payload['approved_selection_count'] = $approved_selection_count;
            $action_payload['approved_selected_entity_keys'] = $approved_entity_keys;
            $action_payload['approved_selected_provider_output_ids'] = $approved_provider_output_ids;
            $action_payload['approved_selection_hash'] = $approved_selection_hash;
            $action_payload['approved_max_total_cost_cents'] = $approved_max_total_cost_cents;
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            $action_payload = $this->aysa_agent8_prepare_onpage_meta_apply_payload($action_payload, $project_id);
            if (empty($action_payload['_aysa_onpage_apply_error'])) {
                $prepared_items = is_array($action_payload['accepted_items'] ?? null)
                    ? array_values(array_filter($action_payload['accepted_items'], 'is_array'))
                    : array();
                if (
                    count($prepared_items) !== $approved_selection_count
                    || !hash_equals($approved_selection_hash, sanitize_text_field((string) ($action_payload['payload_hash'] ?? '')))
                    || intval($action_payload['max_total_cost_cents'] ?? 0) !== $approved_max_total_cost_cents
                ) {
                    $action_payload['_aysa_onpage_apply_error'] = 'onpage_approved_selection_changed';
                    $action_payload['_aysa_onpage_apply_error_context'] = array(
                        'selected_count' => $approved_selection_count,
                        'eligible_count' => count($prepared_items),
                        'approved_max_total_cost_cents' => $approved_max_total_cost_cents,
                        'prepared_max_total_cost_cents' => intval($action_payload['max_total_cost_cents'] ?? 0),
                    );
                }
            }
            if (!empty($action_payload['_aysa_onpage_apply_error'])) {
                $apply_error_code = sanitize_key((string) $action_payload['_aysa_onpage_apply_error']);
                $apply_error_messages = array(
                    'onpage_apply_requires_selected_items' => 'Selectează cel puțin un rând Metadata înainte de aplicare.',
                    'onpage_apply_batch_limit_exceeded' => 'Selecția Metadata depășește limita lotului curent. Redu selecția și reîncearcă.',
                    'onpage_qa_smoke_max_three_required' => 'Testul QA permite maximum 3 rânduri Metadata.',
                    'onpage_workspace_unavailable' => 'Inventarul On Page nu este disponibil momentan. Redeschide review-ul și reîncearcă.',
                    'onpage_business_relevance_required' => 'Selecția conține pagini care nu mai sunt eligibile pentru aplicare sigură. Verifică rândurile și reîncearcă.',
                    'onpage_workspace_hash_mismatch' => 'Selecția Metadata s-a schimbat față de review. Redeschide tabul pentru a folosi lista actuală.',
                    'onpage_approved_cost_cap_mismatch' => 'Plafonul aprobat nu mai corespunde exact selecției Metadata. Aplicarea a fost oprită înainte de orice scriere.',
                    'onpage_approved_selection_changed' => 'Selecția Metadata s-a schimbat după confirmare. Aplicarea a fost oprită înainte de orice scriere.',
                );
                $apply_error_context = isset($action_payload['_aysa_onpage_apply_error_context']) && is_array($action_payload['_aysa_onpage_apply_error_context'])
                    ? $action_payload['_aysa_onpage_apply_error_context']
                    : array();
                $apply_error_message = (string) ($apply_error_messages[$apply_error_code] ?? 'Aplicarea Metadata a fost oprită în siguranță. Redeschide review-ul și reîncearcă.');
                if ($apply_error_code === 'onpage_workspace_hash_mismatch' && $apply_error_context !== array()) {
                    $apply_error_message .= sprintf(
                        ' Selectate: %d; eligibile acum: %d.',
                        intval($apply_error_context['selected_count'] ?? 0),
                        intval($apply_error_context['eligible_count'] ?? 0)
                    );
                    $changed_labels = array_values(array_filter(array_map(static function($row) {
                        if (!is_array($row)) {
                            return '';
                        }
                        $title = sanitize_text_field((string) ($row['title'] ?? ''));
                        $reason = sanitize_key((string) ($row['reason'] ?? ''));
                        $reason_labels = array(
                            'business_relevance_review_required' => 'relevanța trebuie reverificată',
                            'already_applied' => 'metadata este deja aplicată',
                            'proposal_package_missing' => 'propunerea metadata lipsește',
                            'workspace_row_missing' => 'rândul nu mai există în review',
                        );
                        if ($title === '') {
                            return '';
                        }
                        return isset($reason_labels[$reason])
                            ? $title . ' (' . $reason_labels[$reason] . ')'
                            : $title;
                    }, is_array($apply_error_context['changed_rows'] ?? null) ? $apply_error_context['changed_rows'] : array())));
                    if ($changed_labels !== array()) {
                        $apply_error_message .= ' Verifică din nou: ' . implode(', ', array_slice($changed_labels, 0, 3)) . '.';
                    }
                }
                wp_send_json_error(array(
                    'code' => $apply_error_code,
                    'message' => $apply_error_message,
                    'details' => $apply_error_context,
                ), 409);
                return;
            }
        }
        if (
            $agent_action === 'execute_onpage_plan'
            && sanitize_key((string) ($action_payload['section'] ?? '')) === 'aeo_content_qa'
            && sanitize_key((string) ($action_payload['operation'] ?? '')) === 'apply_rewrite'
        ) {
            wp_send_json_error(array(
                'code' => 'onpage_aeo_appendix_contract_needed',
                'message' => 'AEO Appendix apply este blocat până există diff pe câmpuri, readback, istoric, restore și billing doar după verificare.',
                'write_class' => 'onpage_aeo_appendix_apply',
                'writes_enabled' => false,
                'billing_enabled' => false,
            ), 409);
            return;
            if (empty($action_payload['confirmed_by_user'])) {
                wp_send_json_error(array(
                    'code' => 'onpage_apply_requires_confirmation',
                    'message' => 'Aplicarea On Page necesită confirmare explicită înainte de a scrie în WordPress.',
                ), 400);
                return;
            }
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            $action_payload = $this->aysa_agent8_prepare_onpage_aeo_apply_payload($action_payload, $project_id);
            if (!empty($action_payload['_aysa_onpage_apply_error'])) {
                wp_send_json_error(array(
                    'code' => sanitize_key((string) $action_payload['_aysa_onpage_apply_error']),
                    'message' => 'Datele On Page s-au schimbat. Reîmprospătează review-ul înainte de aplicare.',
                ), 409);
                return;
            }
        }
        if ($agent_action === 'start_onpage_seo_run') {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            $action_payload = $this->aysa_agent8_prepare_onpage_start_payload($action_payload, $project_id);
            if (!empty($action_payload['_aysa_onpage_start_error'])) {
                wp_send_json_error(array(
                    'code' => sanitize_key((string) $action_payload['_aysa_onpage_start_error']),
                    'message' => 'Nu am găsit pagini sau produse eligibile pentru On Page SEO. Reîmprospătează inventarul și încearcă din nou.',
                    'onpage_start_context_status' => isset($action_payload['onpage_start_context_status']) && is_array($action_payload['onpage_start_context_status'])
                        ? $action_payload['onpage_start_context_status']
                        : array(),
                ), 409);
                return;
            }
        }
        $this->handle_agent8v3_proxy('POST', '/v1/agent8v3/action', array(
            'action' => $agent_action,
            'payload' => $action_payload,
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'state_version' => isset($_POST['state_version']) ? intval((string) wp_unslash($_POST['state_version'])) : 0,
        ));
    }

    public function handle_agent8v3_onpage_start_preflight() {
        $action_payload = array();
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        if (isset($_POST['payload'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
            $decoded_payload = json_decode((string) wp_unslash($_POST['payload']), true);
            if (is_array($decoded_payload)) {
                $action_payload = $decoded_payload;
            }
        }

        $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
        $action_payload = $this->aysa_agent8_prepare_onpage_start_payload($action_payload, $project_id);
        if (!empty($action_payload['_aysa_onpage_start_error'])) {
            wp_send_json_error(array(
                'code' => sanitize_key((string) $action_payload['_aysa_onpage_start_error']),
                'message' => 'Nu am găsit pagini sau produse eligibile pentru On Page SEO. Reîmprospătează inventarul și încearcă din nou.',
                'onpage_start_context_status' => isset($action_payload['onpage_start_context_status']) && is_array($action_payload['onpage_start_context_status'])
                    ? $action_payload['onpage_start_context_status']
                    : array(),
            ), 409);
            return;
        }

        $this->handle_agent8v3_proxy('POST', '/v1/agent8v3/preflight', array(
            'action' => 'start_onpage_seo_run',
            'payload' => $action_payload,
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'state_version' => isset($_POST['state_version']) ? intval((string) wp_unslash($_POST['state_version'])) : 0,
        ));
    }

    public function handle_agent8v3_business_profile_preflight() {
        $this->handle_agent8v3_proxy('POST', '/v1/agent8v3/preflight', array(
            'action' => 'run_business_profile_autofill',
            'payload' => array(),
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'state_version' => isset($_POST['state_version']) ? intval((string) wp_unslash($_POST['state_version'])) : 0,
        ));
    }

    public function handle_agent8v3_competition_keyword_preflight() {
        $this->handle_agent8v3_proxy('POST', '/v1/agent8v3/preflight', array(
            'action' => 'run_competition_keyword_analysis',
            'payload' => array(),
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'state_version' => isset($_POST['state_version']) ? intval((string) wp_unslash($_POST['state_version'])) : 0,
        ));
    }

    public function handle_agent8v3_keyword_research_preflight() {
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- The delegated canonical proxy verifies the same AJAX nonce before any network or state action.
        $agent_action = isset($_POST['agent_action']) ? sanitize_key((string) wp_unslash($_POST['agent_action'])) : 'start_keyword_research_run';
        if (!in_array($agent_action, array('start_keyword_research_run', 'retry_keyword_research_run'), true)) {
            $agent_action = 'start_keyword_research_run';
        }
        $this->handle_agent8v3_proxy('POST', '/v1/agent8v3/preflight', array(
            'action' => $agent_action,
            'payload' => array(),
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'state_version' => isset($_POST['state_version']) ? intval((string) wp_unslash($_POST['state_version'])) : 0,
        ));
    }

    public function handle_agent8v3_fixed_cost_preflight() {
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- The delegated canonical proxy verifies the same AJAX nonce before the read-only broker request.
        $agent_action = isset($_POST['agent_action']) ? sanitize_key((string) wp_unslash($_POST['agent_action'])) : '';
        $allowed_actions = array(
            'save_objectives',
            'run_seo_profile_autofill',
            'save_seo_profile',
            'confirm_competition_keyword',
            'select_tone_of_voice_friendly',
            'select_tone_of_voice_professional',
            'select_tone_of_voice_expert',
            'select_tone_of_voice_premium',
            'generate_content_strategy',
            'generate_content_article',
            'find_offpage_link_exchange_matches',
            'generate_offpage_gbp_social_calendar',
        );
        if (!in_array($agent_action, $allowed_actions, true)) {
            wp_send_json_error(array(
                'code' => 'unsupported_fixed_cost_preflight_action',
                'message' => 'Acțiunea taxabilă cerută nu are un contract de preflight aprobat.',
            ), 400);
            return;
        }

        $action_payload = array();
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- The delegated canonical proxy verifies the same AJAX nonce before the read-only broker request.
        if (isset($_POST['payload'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Structured input is forwarded only to the canonical read-only preflight normalizers.
            $decoded_payload = json_decode((string) wp_unslash($_POST['payload']), true);
            if (is_array($decoded_payload)) {
                $action_payload = $decoded_payload;
            }
        }

        $this->handle_agent8v3_proxy('POST', '/v1/agent8v3/preflight', array(
            'action' => $agent_action,
            'payload' => $action_payload,
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- The delegated canonical proxy verifies the same AJAX nonce before the read-only broker request.
            'state_version' => isset($_POST['state_version']) ? intval((string) wp_unslash($_POST['state_version'])) : 0,
        ));
    }

    private function aysa_agent8v3_site_audit_modal_snapshot($project_id, $tab = '') {
        $tab = sanitize_key((string) $tab);
        if ($tab === '') {
            $tab = 'overview';
        }
        if (in_array($tab, array('overview', 'summary', 'issues', 'pages', 'exports'), true)) {
            return $this->aysa_agent8v3_site_audit_overview_snapshot($project_id);
        }
        $needs_action_bundles = in_array($tab, array(
            'fixes',
            'action_packages',
            'metadata_preview',
            'canonical_preview',
            'schema_preview',
            'image_optimization_preview',
            'internal_linking_preview',
            'internal_linking_history',
        ), true);

        return $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id, $needs_action_bundles);
    }

    private function aysa_agent8v3_site_audit_overview_snapshot($project_id) {
        $project_id = $this->aysa_resolve_technical_project_id(intval($project_id));
        $state = get_option($this->aysa_technical_site_audit_state_key($project_id), array());
        if (!is_array($state)) {
            $state = array();
        }
        if (empty($state)) {
            $latest_version = $this->aysa_sf_site_audit_get_latest_version_row($project_id);
            if (is_array($latest_version) && !empty($latest_version)) {
                $state = $this->aysa_sf_site_audit_build_state_from_version_row($latest_version);
            }
        }

        $audit = is_array($state['audit_snapshot'] ?? null) ? $state['audit_snapshot'] : array();
        $overview = is_array($audit['overview'] ?? null) ? $audit['overview'] : array();
        $issues = is_array($audit['issues'] ?? null) ? array_values($audit['issues']) : array();
        $pages = is_array($audit['pages'] ?? null) ? array_values($audit['pages']) : array();
        $files = is_array($state['result_files'] ?? null) ? array_values($state['result_files']) : array();
        if (($issues === array() || $files === array()) && !empty($state)) {
            $latest_version = $this->aysa_sf_site_audit_get_latest_version_row($project_id);
            if (is_array($latest_version) && !empty($latest_version)) {
                $latest_state = $this->aysa_sf_site_audit_build_state_from_version_row($latest_version);
                if (is_array($latest_state) && !empty($latest_state)) {
                    $latest_audit = is_array($latest_state['audit_snapshot'] ?? null) ? $latest_state['audit_snapshot'] : array();
                    $latest_issues = is_array($latest_audit['issues'] ?? null) ? array_values($latest_audit['issues']) : array();
                    $latest_pages = is_array($latest_audit['pages'] ?? null) ? array_values($latest_audit['pages']) : array();
                    $latest_files = is_array($latest_state['result_files'] ?? null) ? array_values($latest_state['result_files']) : array();
                    if ($latest_issues !== array()) {
                        $state = $latest_state;
                        $audit = $latest_audit;
                        $overview = is_array($audit['overview'] ?? null) ? $audit['overview'] : array();
                        $issues = $latest_issues;
                        $pages = $latest_pages;
                    }
                    if ($latest_files !== array()) {
                        $files = $latest_files;
                    }
                }
            }
        }
        $csv_file_count = 0;
        foreach ($files as $file) {
            if (!is_array($file)) {
                continue;
            }
            $filename = strtolower((string) ($file['filename'] ?? ''));
            $type = strtolower((string) ($file['type'] ?? ''));
            if ($type === 'csv' || substr($filename, -4) === '.csv') {
                $csv_file_count++;
            }
        }

        $issue_rows = array();
        foreach (array_slice($issues, 0, 12) as $issue) {
            if (!is_array($issue)) {
                continue;
            }
            $key = sanitize_key((string) ($issue['key'] ?? ''));
            $title = sanitize_text_field((string) ($issue['title'] ?? $issue['issue'] ?? $key));
            $description = sanitize_text_field((string) ($issue['description'] ?? ''));
            $copy = $this->aysa_adjustments_site_audit_issue_copy_by_key($key);
            $details = is_array($issue['details'] ?? null) ? $issue['details'] : array();
            $sample_rows = is_array($details['sample_rows'] ?? null) ? array_values($details['sample_rows']) : array();
            $examples = array();
            foreach (array_slice($sample_rows, 0, 3) as $sample_row) {
                if (!is_array($sample_row)) {
                    continue;
                }
                $url = esc_url_raw((string) ($sample_row['url'] ?? $sample_row['Address'] ?? $sample_row['"Address"'] ?? $sample_row['﻿"Address"'] ?? ''));
                if ($url === '') {
                    continue;
                }
                $examples[] = array(
                    'url' => $url,
                    'title' => sanitize_text_field((string) ($sample_row['title'] ?? $sample_row['Title'] ?? '')),
                );
            }
            $issue_rows[] = array(
                'key' => $key,
                'title' => sanitize_text_field((string) ($copy['title'] ?? $title)),
                'name' => sanitize_text_field((string) ($copy['title'] ?? $title)),
                'status' => sanitize_key((string) ($issue['severity'] ?? 'review')),
                'details' => sanitize_text_field((string) ($copy['impact'] ?? $description)),
                'impact_plain' => sanitize_text_field((string) ($copy['impact'] ?? $description)),
                'recommended_action_plain' => sanitize_text_field((string) ($copy['action'] ?? 'Revizuiește problema înainte de orice aplicare.')),
                'affected_pages_count' => intval($issue['affected_pages_count'] ?? $issue['affected_pages'] ?? count($examples)),
                'affected_pages' => intval($issue['affected_pages_count'] ?? $issue['affected_pages'] ?? count($examples)),
                'examples' => $examples,
            );
        }

        $page_rows = array();
        foreach (array_slice($pages, 0, 12) as $page) {
            if (!is_array($page)) {
                continue;
            }
            $page_rows[] = array(
                'url' => esc_url_raw((string) ($page['url'] ?? '')),
                'title' => sanitize_text_field((string) ($page['title'] ?? '')),
                'status_code' => intval($page['status_code'] ?? 0),
                'indexability' => sanitize_text_field((string) ($page['indexability'] ?? '')),
                'crawl_depth' => intval($page['crawl_depth'] ?? 0),
                'inlinks' => intval($page['inlinks'] ?? 0),
            );
        }

        $pages_crawled = intval($overview['pages_crawled'] ?? 0);
        $valid_crawl = $pages_crawled > 0 && $csv_file_count > 0;

        return array(
            'id' => 'technical_site_audit',
            'status' => sanitize_key((string) ($state['status'] ?? 'idle')),
            'valid_crawl' => $valid_crawl,
            'job_id' => sanitize_text_field((string) ($state['job_id'] ?? '')),
            'version_id' => intval($state['version_id'] ?? 0),
            'version_number' => intval($state['version_number'] ?? 0),
            'target_url' => esc_url_raw((string) ($state['target_url'] ?? '')),
            'updated_at' => sanitize_text_field((string) ($state['updated_at'] ?? '')),
            'mode' => sanitize_text_field((string) ($state['mode'] ?? 'baseline_full')),
            'provider' => sanitize_text_field((string) ($state['provider'] ?? 'screamingfrog')),
            'pages_crawled' => $pages_crawled,
            'artifact_count' => count($files),
            'csv_artifact_count' => $csv_file_count,
            'site_health' => intval($overview['site_health'] ?? 0),
            'errors' => intval($overview['errors'] ?? 0),
            'warnings' => intval($overview['warnings'] ?? 0),
            'count' => count($issues),
            'rows' => $issue_rows,
            'issues' => $issue_rows,
            'pages' => $page_rows,
            'files' => array_slice($files, 0, 12),
            'result_files' => array_slice($files, 0, 12),
            'conclusion' => array(
                'top_issues' => $issue_rows,
                'recommended_actions' => array(),
            ),
            'has_data' => $valid_crawl && (!empty($audit) || !empty($files)),
        );
    }

    private function aysa_agent8v3_technical_overview_decision_site_audit_snapshot($project_id) {
        $project_id = $this->aysa_resolve_project_id_with_local_fallback(intval($project_id));
        return $this->aysa_agent8_transport_reduce_technical_site_audit_snapshot(
            $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id, false)
        );
    }

    public function handle_agent8v3_modal() {
        $agent8_project_id = $this->aysa_agent8v3_project_id();
        $agent8_site_host = $this->aysa_agent8v3_site_host();
        $agent8_wp_project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
        $filters = array();
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        if (isset($_POST['filters'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
            $decoded_filters = json_decode((string) wp_unslash($_POST['filters']), true);
            if (is_array($decoded_filters)) {
                $filters = $decoded_filters;
            }
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $modal_key = isset($_POST['modal_key']) ? sanitize_key((string) wp_unslash($_POST['modal_key'])) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $conversation_language = isset($_POST['conversation_language']) ? sanitize_key((string) wp_unslash($_POST['conversation_language'])) : 'en';
        if (!in_array($conversation_language, array('en', 'ro', 'bg'), true)) {
            $conversation_language = 'en';
        }
        $payload = array(
            // Modal projections need the same canonical scope as state/action calls.
            // This is read-only context, not a Monitoring refresh or policy mutation.
            'project_id' => $agent8_project_id,
            'site_host' => $agent8_site_host,
            'wp_project_id' => $agent8_wp_project_id,
            'modal_key' => $modal_key,
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'state_version' => isset($_POST['state_version']) ? intval((string) wp_unslash($_POST['state_version'])) : 0,
            'conversation_language' => $conversation_language,
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'page' => isset($_POST['page']) ? max(1, intval((string) wp_unslash($_POST['page']))) : 1,
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'per_page' => isset($_POST['per_page']) ? max(5, min(100, intval((string) wp_unslash($_POST['per_page'])))) : 25,
            'filters' => $filters,
        );
        $technical_site_audit_tab = sanitize_key((string) ($filters['tab'] ?? 'overview'));
        $technical_site_audit_safe_bundle = $modal_key === 'technical_site_audit' && $technical_site_audit_tab === 'safe_bundle';
        if ($modal_key === 'technical_sitemap_robots') {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            $target_url = $this->aysa_technical_resolve_target_url($project_id);
            $sitemap_robots_snapshot = $this->aysa_adjustments_get_technical_sitemap_robots_snapshot($project_id);
            if (!is_array($sitemap_robots_snapshot) || empty($sitemap_robots_snapshot['has_data'])) {
                $sitemap_robots_snapshot = array(
                    'id' => 'technical_sitemap_robots',
                    'status' => 'idle',
                    'target_url' => $target_url,
                    'rows' => array(),
                    'sitemaps' => array(),
                    'robots_rows' => array(),
                    'generation' => array(),
                    'has_data' => false,
                );
            }
            $sitemap_robots_snapshot = $this->aysa_agent8v3_hydrate_sitemap_robots_public_readback_snapshot(
                $sitemap_robots_snapshot,
                $project_id,
                $target_url
            );
            $sitemap_robots_snapshot['sitemap_authority_active_takeover'] = $this->aysa_technical_sitemap_authority_latest_takeover_history(
                $project_id,
                $target_url
            );
            $payload['technical_sitemap_robots_snapshot'] = $sitemap_robots_snapshot;
        }
        if ($modal_key === 'technical_internal_linking') {
            $payload['technical_internal_linking_snapshot'] = $this->aysa_agent8_transport_reduce_technical_internal_linking_snapshot(
                $this->aysa_adjustments_get_technical_internal_linking_snapshot(
                    $this->aysa_resolve_project_id_with_local_fallback(0)
                )
            );
        }
        if ($modal_key === 'technical_image_alt') {
            $image_alt_project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            $image_alt_snapshot = $this->aysa_agent8v3_site_audit_modal_snapshot(
                $image_alt_project_id,
                ''
            );
            $image_alt_inventory_items = $this->aysa_technical_site_audit_discover_image_alt_items_from_wp($image_alt_project_id);
            $image_alt_snapshot['image_alt'] = array(
                'source' => 'wordpress_readonly_image_alt_inventory',
                'status' => 'inventory_ready',
                'inventory_summary' => $this->aysa_technical_site_audit_image_alt_inventory_summary($image_alt_inventory_items),
                'image_alt_inventory_package' => array(
                    'package_id' => 'readonly_image_alt_inventory_' . $image_alt_project_id,
                    'action_key' => 'image_alt_inventory_coverage',
                    'source' => 'wordpress_readonly_image_alt_inventory',
                    'items' => array(),
                    'skipped' => array_values($image_alt_inventory_items),
                    'total_candidates' => count($image_alt_inventory_items),
                    'safe_auto_apply_count' => 0,
                    'manual_review_needed_count' => count($image_alt_inventory_items),
                    'inventory_summary' => $this->aysa_technical_site_audit_image_alt_inventory_summary($image_alt_inventory_items),
                    'automatic_site_mutation' => false,
                ),
            );
            $payload['product_gates'] = $this->aysa_agent8v3_image_alt_product_gates();
            $payload['technical_site_audit'] = $this->aysa_agent8_transport_reduce_technical_site_audit_snapshot(
                $image_alt_snapshot
            );
        }
        if ($modal_key === 'technical_overview' && !is_array($payload['technical_site_audit'] ?? null)) {
            $payload['technical_site_audit'] = $this->aysa_agent8v3_technical_overview_decision_site_audit_snapshot(
                $this->aysa_resolve_project_id_with_local_fallback(0)
            );
        }
        if (!$technical_site_audit_safe_bundle && $modal_key === 'technical_site_audit' && !is_array($payload['technical_site_audit'] ?? null)) {
            $payload['technical_site_audit'] = $this->aysa_agent8_transport_reduce_technical_site_audit_snapshot(
                $this->aysa_agent8v3_site_audit_modal_snapshot(
                    $this->aysa_resolve_project_id_with_local_fallback(0),
                    $technical_site_audit_tab
                )
            );
        }
        if ($modal_key === 'technical_site_audit' && in_array($technical_site_audit_tab, array('internal_linking_preview', 'internal_linking_history'), true)) {
            $payload['technical_internal_linking_snapshot'] = $this->aysa_agent8_transport_reduce_technical_internal_linking_snapshot(
                $this->aysa_adjustments_get_technical_internal_linking_snapshot(
                    $this->aysa_resolve_project_id_with_local_fallback(0)
                )
            );
        }
        $technical_site_audit_internal_linking_tab = $modal_key === 'technical_site_audit'
            && in_array($technical_site_audit_tab, array('internal_linking_preview', 'internal_linking_history'), true);
        if (in_array($modal_key, array('technical_overview', 'technical_site_audit'), true) && !$technical_site_audit_internal_linking_tab) {
            $technical_project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            $technical_tab = $technical_site_audit_tab;
            $schema_preview_rows = $this->aysa_agent8v3_schema_controlled_candidates_rows();
            $payload['schema_apply'] = array(
                'product_gates' => $this->aysa_agent8v3_schema_product_gates(),
            );
            if ($schema_preview_rows !== array()) {
                $payload['schema_preview'] = array(
                    'source' => 'plugin_controlled_schema_candidates',
                    'rows' => $schema_preview_rows,
                );
            }
            $metadata_snapshot = $modal_key === 'technical_site_audit'
                ? $this->aysa_agent8v3_site_audit_modal_snapshot($technical_project_id, $technical_tab)
                : $this->aysa_adjustments_get_technical_site_audit_snapshot($technical_project_id, false);
            $metadata_context = $this->aysa_technical_site_audit_build_package_adapter_context(
                $technical_project_id,
                'prepare_metadata_remediation',
                array('technical_site_audit' => is_array($metadata_snapshot) ? $metadata_snapshot : array())
            );
            if (is_array($metadata_context['metadata_posts'] ?? null)) {
                $metadata_preview_rows = $this->aysa_agent8_transport_metadata_preview_rows_from_route_evidence_posts($metadata_context['metadata_posts']);
                $payload['metadata_route_evidence'] = array(
                    'source' => 'wordpress_adapter_context',
                    'metadata_posts' => $this->aysa_agent8_transport_reduce_metadata_route_evidence_posts($metadata_context['metadata_posts']),
                );
                if ($metadata_preview_rows !== array()) {
                    $payload['metadata_preview'] = array(
                        'source' => 'wordpress_route_evidence_review_rows',
                        'rows' => $metadata_preview_rows,
                    );
                }
            }
            if ($modal_key === 'technical_overview' || ($modal_key === 'technical_site_audit' && $technical_tab === 'safe_bundle')) {
                $image_optimization_rows = $this->aysa_agent8v3_image_optimization_crawl_scoped_candidates_rows($technical_project_id);
                if ($image_optimization_rows !== array()) {
                    if (is_array($payload['technical_site_audit'] ?? null)) {
                        $payload['technical_site_audit']['image_optimization_preview'] = array(
                            'source' => 'plugin_crawl_scoped_image_optimization_candidates',
                            'rows' => $image_optimization_rows,
                        );
                    }
                    $payload['image_optimization_apply'] = array(
                        'product_gates' => $this->aysa_agent8v3_image_optimization_product_gates(),
                    );
                    $payload['image_optimization_preview'] = array(
                        'source' => 'plugin_crawl_scoped_image_optimization_candidates',
                        'rows' => $image_optimization_rows,
                    );
                }
            }
        }
        if ($modal_key === 'technical_site_audit' && sanitize_key((string) ($filters['tab'] ?? '')) === 'metadata_preview') {
            $payload['metadata_apply'] = array(
                'product_gates' => $this->aysa_agent8v3_metadata_product_gates(),
            );
        }
        if ($modal_key === 'technical_site_audit' && sanitize_key((string) ($filters['tab'] ?? '')) === 'schema_preview') {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            $snapshot = $this->aysa_agent8v3_site_audit_modal_snapshot($project_id, 'schema_preview');
            $schema_preview_rows = $this->aysa_agent8v3_schema_controlled_candidates_rows();
            if ($schema_preview_rows !== array()) {
                $snapshot['schema_preview'] = array(
                    'source' => 'plugin_controlled_schema_candidates',
                    'rows' => $schema_preview_rows,
                );
            }
            $payload['technical_site_audit'] = $this->aysa_agent8_transport_reduce_technical_site_audit_snapshot($snapshot);
            $payload['schema_apply'] = array(
                'product_gates' => $this->aysa_agent8v3_schema_product_gates(),
            );
            $payload['schema_preview'] = array(
                'source' => 'plugin_controlled_schema_candidates',
                'rows' => $schema_preview_rows,
            );
        }
        if ($modal_key === 'technical_schema') {
            $schema_preview_rows = $this->aysa_agent8v3_schema_controlled_candidates_rows();
            $payload['schema_apply'] = array(
                'product_gates' => $this->aysa_agent8v3_schema_product_gates(),
            );
            $payload['schema_preview'] = array(
                'source' => 'plugin_controlled_schema_candidates',
                'rows' => $schema_preview_rows,
            );
        }
        if ($modal_key === 'technical_site_audit' && sanitize_key((string) ($filters['tab'] ?? '')) === 'image_optimization_preview') {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            $snapshot = $this->aysa_agent8v3_site_audit_modal_snapshot($project_id, 'image_optimization_preview');
            $image_optimization_rows = $this->aysa_agent8v3_image_optimization_crawl_scoped_candidates_rows($project_id);
            if ($image_optimization_rows !== array()) {
                $snapshot['image_optimization_preview'] = array(
                    'source' => 'plugin_crawl_scoped_image_optimization_candidates',
                    'rows' => $image_optimization_rows,
                );
            }
            $payload['technical_site_audit'] = $this->aysa_agent8_transport_reduce_technical_site_audit_snapshot($snapshot);
            $payload['image_optimization_apply'] = array(
                'product_gates' => $this->aysa_agent8v3_image_optimization_product_gates(),
            );
            $payload['image_optimization_preview'] = array(
                'source' => 'plugin_crawl_scoped_image_optimization_candidates',
                'rows' => $image_optimization_rows,
            );
        }
        if ($modal_key === 'technical_site_audit' && sanitize_key((string) ($filters['tab'] ?? '')) === 'internal_linking_preview') {
            $payload['internal_linking_apply'] = array(
                'product_gates' => $this->aysa_agent8v3_internal_linking_product_gates(),
            );
        }
        if ($modal_key === 'technical_site_audit' && in_array(sanitize_key((string) ($filters['tab'] ?? '')), array('internal_linking_history', 'safe_bundle'), true)) {
            $payload['internal_linking_history'] = array(
                'rows' => $this->aysa_agent8v3_internal_linking_history_projection_rows(),
                'product_gates' => $this->aysa_agent8v3_internal_linking_product_gates(),
            );
        }
        if ($modal_key === 'technical_pagespeed') {
            $payload['technical_pagespeed_snapshot'] = $this->aysa_adjustments_get_technical_pagespeed_snapshot(
                $this->aysa_resolve_project_id_with_local_fallback(0)
            );
        }
        if ($modal_key === 'onpage_seo') {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            // The default drawer entry is the lightweight overview. Do not run the
            // full keyword-mapping preparation path merely to open a read-only modal.
            $onpage_tab = isset($filters['tab']) ? sanitize_key((string) $filters['tab']) : 'overview';
            if ($onpage_tab === '') {
                $onpage_tab = 'overview';
            }
            $is_execution_report = $onpage_tab === 'execution_report';
            $onpage_profile_enabled = !empty($filters['_profile_hydration']);
            $onpage_profile_started_at = microtime(true);
            $onpage_profile = array(
                'modal_tab' => $onpage_tab,
                'profile_version' => 'fix30-v1',
            );
            $onpage_profile_mark = static function ($stage) use (&$onpage_profile, $onpage_profile_started_at) {
                $onpage_profile[(string) $stage] = round((microtime(true) - $onpage_profile_started_at) * 1000, 1);
            };
            $onpage_profile_mark('start');

            $meta_review_queue = array();
            $meta_generation_run_id = 0;
            $projection_sync_mode = isset($filters['_sync_projection_mode'])
                ? sanitize_key((string) $filters['_sync_projection_mode'])
                : 'review';
            $projection_run_types_for_tab = $this->aysa_agent8v3_onpage_projection_run_types_for_tab($onpage_tab, $projection_sync_mode);
            $projection_sync_supported = $projection_run_types_for_tab !== array()
                || $projection_sync_mode === 'apply_readback';
            $skip_projection_sync = false;
            if (in_array($onpage_tab, array('overview', 'meta_snippets'), true)) {
                // Metadata review is backed by a durable queue. Read that queue first so
                // the compact overview and the detailed tab report the same generated
                // results without requiring a projection/worker hydration pass.
                $meta_generation_run_id = intval($filters['source_run_id'] ?? $filters['run_id'] ?? 0);
                $meta_review_queue = $this->aysa_agent8_get_onpage_durable_review_queue(
                    $project_id,
                    80,
                    $meta_generation_run_id
                );
                $payload['onpage_review_queue'] = $meta_review_queue;
                $onpage_workspace_service = $this->aysa_onpage_workspace_service();
                if ($onpage_workspace_service && method_exists($onpage_workspace_service, 'get_provider_output_run_options')) {
                    $payload['onpage_metadata_run_options'] = $onpage_workspace_service->get_provider_output_run_options($project_id, 20);
                }
                // The durable queue keeps ordinary review hydration cheap, but it must
                // never suppress the explicit terminal apply/readback bridge. That
                // bridge owns the confirmed WordPress write, verification and billing.
                $skip_projection_sync = in_array($onpage_tab, array('overview', 'meta_snippets'), true)
                    && !empty($meta_review_queue['rows'])
                    && $projection_sync_mode !== 'apply_readback';
            }
            $onpage_profile_mark('durable_queue');
            $missing_current_meta_queue = in_array($onpage_tab, array('overview', 'meta_snippets'), true)
                && $meta_generation_run_id > 0
                && empty($meta_review_queue['rows']);

            $prepare_start_context = !empty($filters['_prepare_start_context']);
            if (!$is_execution_report && $prepare_start_context) {
                $coverage_preview = $this->aysa_agent8_prepare_onpage_start_payload(array('batch_size' => 80), $project_id);
                if (empty($coverage_preview['_aysa_onpage_start_error']) && is_array($coverage_preview['onpage_coverage'] ?? null)) {
                    $payload['onpage_coverage_snapshot'] = $coverage_preview['onpage_coverage'];
                }
            } elseif (!$is_execution_report) {
                // The overview and ordinary review tabs must remain responsive. The
                // explicit start-context action still owns the full inventory scan.
                $payload['onpage_start_context_status'] = array(
                    'status' => 'deferred',
                    'reason' => 'full_inventory_available_on_explicit_start',
                    'read_only' => true,
                );
            }
            $onpage_profile_mark('start_context');
            if ($onpage_tab === 'keyword_mapping') {
                $payload['keyword_mapping_sync'] = $this->aysa_keyword_mapping_ensure_local_associations($project_id, false);
            }
            if (
                (!$is_execution_report || $projection_sync_mode === 'apply_readback')
                && (!empty($filters['_sync_projection']) || $missing_current_meta_queue)
                && $projection_sync_supported
                && !$skip_projection_sync
            ) {
                $projection_budget_seconds = in_array($onpage_tab, array('content', 'aeo_content_qa', 'links_authority', 'images'), true)
                    ? 7
                    : 20;
                $payload['onpage_projection_sync'] = $this->aysa_agent8v3_sync_latest_onpage_projection_snapshot($project_id, $onpage_tab, $projection_sync_mode, $projection_budget_seconds);
                if (is_array($payload['onpage_projection_sync'] ?? null) && intval($payload['onpage_projection_sync']['state_version'] ?? 0) > 0) {
                    $payload['state_version'] = intval($payload['onpage_projection_sync']['state_version']);
                }
                if (is_array($payload['onpage_projection_sync']['onpage_coverage'] ?? null)
                    && $payload['onpage_projection_sync']['onpage_coverage'] !== array()) {
                    $payload['onpage_coverage_snapshot'] = $payload['onpage_projection_sync']['onpage_coverage'];
                }
                if (
                    in_array($onpage_tab, array('overview', 'meta_snippets'), true)
                    && $missing_current_meta_queue
                ) {
                    // A terminal run can finish while its review drawer is closed.
                    // Re-read the exact durable queue after the bounded projection
                    // sync so the first later open does not render a stale prior run.
                    $meta_review_queue = $this->aysa_agent8_get_onpage_durable_review_queue(
                        $project_id,
                        80,
                        $meta_generation_run_id
                    );
                    $payload['onpage_review_queue'] = $meta_review_queue;
                }
            } elseif ($skip_projection_sync) {
                $payload['onpage_projection_sync'] = array(
                    'status' => 'skipped',
                    'reason' => 'durable_review_queue_ready',
                );
            } elseif (!empty($filters['_sync_projection']) && !$projection_sync_supported) {
                $payload['onpage_projection_sync'] = array(
                    'status' => 'skipped',
                    'reason' => 'read_only_tab_has_no_worker_projection',
                );
            }
            $onpage_profile_mark('projection_sync');
            if ($onpage_tab === 'execution_report') {
                $requested_snapshot_identity = sanitize_text_field((string) ($filters['snapshot_identity'] ?? ''));
                $scoped_execution_snapshot = is_array($payload['onpage_projection_sync']['execution_snapshot'] ?? null)
                    && $payload['onpage_projection_sync']['execution_snapshot'] !== array()
                    ? $payload['onpage_projection_sync']['execution_snapshot']
                    : array();
                $requested_source_run_id = intval($filters['source_run_id'] ?? $filters['run_id'] ?? 0);
                $requested_idempotency_key = sanitize_text_field((string) ($filters['idempotency_key'] ?? ''));
                $has_requested_snapshot = $requested_snapshot_identity !== '' || $requested_source_run_id > 0 || $requested_idempotency_key !== '';
                $selected_execution_snapshot = array();
                if ($requested_snapshot_identity !== '' || $requested_source_run_id > 0 || $requested_idempotency_key !== '') {
                    $selected_execution_snapshot = $this->aysa_agent8v3_find_onpage_execution_snapshot(
                        $requested_source_run_id,
                        $requested_idempotency_key,
                        $requested_snapshot_identity
                    );
                } elseif ($scoped_execution_snapshot !== array()) {
                    $selected_execution_snapshot = $scoped_execution_snapshot;
                } else {
                    $selected_execution_snapshot = $this->aysa_agent8v3_latest_onpage_execution_snapshot();
                }
                $payload['onpage_execution_snapshot'] = $this->aysa_agent8v3_normalize_onpage_execution_snapshot(
                    $selected_execution_snapshot,
                    $project_id
                );
                $payload['onpage_execution_snapshot_selector'] = array(
                    'status' => $has_requested_snapshot
                        ? ($payload['onpage_execution_snapshot'] !== array() ? 'selected' : 'snapshot_not_found')
                        : 'latest',
                    'requested_identity' => $requested_snapshot_identity,
                    'requested_source_run_id' => $requested_source_run_id,
                    'requested_idempotency_key' => $requested_idempotency_key,
                );
                if ($payload['onpage_execution_snapshot'] !== array()) {
                    $source_run_id = $this->aysa_agent8v3_onpage_execution_source_run_id(
                        $project_id,
                        $payload['onpage_execution_snapshot']
                    );
                    if ($source_run_id > 0) {
                        $payload['onpage_execution_snapshot']['source_run_id'] = $source_run_id;
                    }
                    $payload['onpage_execution_snapshot'] = $this->aysa_agent8v3_overlay_onpage_run_billing_correction(
                        $payload['onpage_execution_snapshot'],
                        $project_id
                    );
                }
            }
            $onpage_per_page = intval($payload['per_page'] ?? 25);
            if ($onpage_tab === 'meta_snippets') {
                $onpage_per_page = max(1, min(5, $onpage_per_page));
            }
            $workspace_snapshot = $onpage_tab === 'execution_report'
                ? $this->aysa_agent8v3_onpage_execution_workspace_snapshot(
                    $project_id,
                    $payload['onpage_execution_snapshot'] ?? array()
                )
                : ($onpage_tab === 'meta_snippets'
                    && !empty($meta_review_queue['rows'])
                    ? $meta_review_queue
                    : $this->aysa_agent8_get_onpage_workspace_snapshot(
                        $project_id,
                        $filters,
                        intval($payload['page'] ?? 1),
                        $onpage_per_page
                    ));
            $payload['onpage_workspace_snapshot'] = $this->aysa_agent8_compact_onpage_modal_snapshot(
                $workspace_snapshot,
                $onpage_tab
            );
            $onpage_profile_mark('workspace');
            if (!isset($payload['onpage_execution_snapshot'])) {
                $payload['onpage_execution_snapshot'] = $this->aysa_agent8v3_normalize_onpage_execution_snapshot(
                    $this->aysa_agent8v3_latest_onpage_execution_snapshot(),
                    $project_id
                );
            }
            if ($is_execution_report) {
                $payload['onpage_execution_history'] = $this->aysa_agent8v3_onpage_execution_history_summary(
                    $project_id,
                    $payload['onpage_execution_snapshot']
                );
                $payload['onpage_execution_history_list'] = $this->aysa_agent8v3_onpage_execution_history_list(
                    is_array($payload['onpage_execution_snapshot'] ?? null)
                        ? $payload['onpage_execution_snapshot']
                        : array()
                );
            } else {
                $payload['onpage_execution_history'] = array();
                $payload['onpage_execution_history_list'] = array();
            }
            if ($onpage_tab === 'keyword_mapping') {
                $payload = $this->aysa_agent8_attach_onpage_keyword_mapping_context($payload, $project_id);
                $payload = $this->aysa_agent8_compact_onpage_keyword_mapping_context($payload);
            } elseif ($onpage_tab === 'content') {
                // Content rows already come from the canonical current
                // inventory. Only accepted local associations may refine the
                // visible page; loading the global Research workspace here can
                // append unrelated unresolved rows and exceed the modal budget.
                $payload['keyword_mapping_workspace'] = array();
                $payload['local_keyword_associations'] = $this->aysa_agent8_compact_keyword_mapping_rows(
                    $this->get_keyword_discovery_local_associations($project_id),
                    400
                );
            }
            if (!$is_execution_report && ($onpage_tab === 'keyword_mapping' || !empty($filters['_sync_keyword_page_map']))) {
                $payload['keyword_page_map_snapshot_sync'] = $this->aysa_agent8_sync_keyword_page_map_snapshot($project_id, 'onpage_modal');
            }
            $onpage_profile_mark('finalize');
            if ($onpage_profile_enabled) {
                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Release-reviewed diagnostic only; the message contains no credentials or request payloads and runs only in opt-in debugging or the administrative uninstall lifecycle.
                error_log('[AYSA On Page hydration] ' . wp_json_encode($onpage_profile));
            }
        }
        if ($modal_key === 'offpage_seo') {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            $offpage_tab = sanitize_key((string) ($filters['tab'] ?? 'overview'));
            if ($offpage_tab === 'link_exchange') {
                $payload['offpage_link_exchange_profile'] = $this->aysa_agent8_get_link_exchange_profile_for_agent8($project_id);
                $payload['offpage_link_exchange_requests'] = $this->aysa_agent8_get_link_exchange_requests_snapshot($project_id);
            }
            if ($offpage_tab === 'link_building_plan') {
                $this->aysa_agent8v3_sync_offpage_backlink_strategy_before_modal($project_id);
            }
            if ($offpage_tab === 'gbp' || $offpage_tab === 'gbp_social_calendar' || $offpage_tab === 'gbp_publish_contract') {
                $payload['offpage_gbp_social_calendar'] = $this->aysa_agent8_get_gbp_social_calendar_snapshot($project_id);
                $payload['offpage_gbp_overview'] = $this->aysa_agent8_get_offpage_gbp_overview_snapshot($project_id, true);
            }
            if ($offpage_tab === 'backlink_audit') {
                $payload['offpage_backlink_audit'] = $this->aysa_agent8_get_offpage_backlink_audit_snapshot($project_id);
            }
            if ($offpage_tab === 'link_building_plan') {
                // The authority plan needs the saved audit as its source evidence.
                // Reuse the same durable local snapshot as the Backlink Audit tab
                // instead of forcing an unrelated API-store discovery on every open.
                $payload['offpage_backlink_audit'] = $this->aysa_agent8_get_offpage_backlink_audit_snapshot($project_id);
                $payload['offpage_backlink_strategy'] = $this->aysa_agent8_compact_offpage_backlink_strategy_snapshot(
                    $this->aysa_agent8_get_offpage_backlink_strategy_snapshot($project_id),
                    $payload['page'],
                    $payload['per_page'],
                    $filters
                );
            }
        }
        if ($modal_key === 'monitoring') {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
            // The modal must use the same normalized SERP source identity as the
            // existing policy confirmation and refresh-preflight actions. Keep
            // the Agent8 envelope scope intact: `lab_9` / the hostname selects
            // the persisted Agent8 policy, while the numeric WP project id is
            // only for the existing worker/preflight contracts.
            $monitoring_context = $this->aysa_monitoring_resolve_context('');
            $payload['canonical_project_id'] = intval($project_id);
            $payload['target'] = (string) ($monitoring_context['target'] ?? '');
            $payload['se_domain'] = (string) ($monitoring_context['se_domain'] ?? 'google.com');
            $payload['location_code'] = intval($monitoring_context['location_code'] ?? 2840);
            $payload['language_code'] = strtolower((string) ($monitoring_context['language_code'] ?? 'en'));
            $payload['monitoring_source_configuration'] = array(
                'se_domain' => $payload['se_domain'],
                'location_code' => $payload['location_code'],
                'language_code' => $payload['language_code'],
                'device' => 'desktop',
            );
            $monitoring_bundle = $this->aysa_agent8_transport_reduce_bundle(array(
                'monitoring_rank_tracking' => $this->aysa_agent8_get_monitoring_rank_tracking_snapshot($project_id),
                'monitoring_ai_overview' => $this->aysa_agent8_get_monitoring_ai_overview_snapshot($project_id),
                'monitoring_query_packs' => $this->aysa_agent8_get_monitoring_query_packs_snapshot($project_id),
            ));
            $payload['monitoring_run_status'] = $this->aysa_agent8v3_sync_monitoring_worker_runs($project_id);
            $payload['monitoring_rank_tracking'] = isset($monitoring_bundle['monitoring_rank_tracking']) && is_array($monitoring_bundle['monitoring_rank_tracking']) ? $monitoring_bundle['monitoring_rank_tracking'] : array();
            $payload['monitoring_ai_overview'] = isset($monitoring_bundle['monitoring_ai_overview']) && is_array($monitoring_bundle['monitoring_ai_overview']) ? $monitoring_bundle['monitoring_ai_overview'] : array();
            $payload['monitoring_query_packs'] = isset($monitoring_bundle['monitoring_query_packs']) && is_array($monitoring_bundle['monitoring_query_packs']) ? $monitoring_bundle['monitoring_query_packs'] : array();
        }
        $this->handle_agent8v3_proxy('POST', '/v1/agent8v3/modal', $payload);
    }

    public function handle_agent8v3_approval() {
        $this->handle_agent8v3_proxy('POST', '/v1/agent8v3/approval', array(
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'approval' => isset($_POST['approval']) ? sanitize_key((string) wp_unslash($_POST['approval'])) : '',
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'state_version' => isset($_POST['state_version']) ? intval((string) wp_unslash($_POST['state_version'])) : 0,
        ));
    }

    public function handle_agent8v3_state() {
        $project_id = $this->aysa_agent8v3_project_id();
        $site_host = $this->aysa_agent8v3_site_host();
        $projection_sync_secret_health = $this->aysa_agent8v3_projection_sync_secret_health(true);
        $query = http_build_query(array(
            'project_id' => $project_id,
            'site_host' => $site_host,
            'client_response' => 'wordpress_compact_v1',
        ), '', '&');
        $this->handle_agent8v3_proxy('GET', '/v1/agent8v3/state' . ($query !== '' ? '?' . $query : ''), array(
            '_plugin_projection_sync_secret_health' => $projection_sync_secret_health,
        ));
    }

    private function aysa_agent8v3_sync_offpage_backlink_strategy_before_modal($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array('status' => 'skipped', 'reason' => 'project_required');
        }
        $site_host = $this->aysa_agent8v3_site_host();
        $response = $this->aysa_agent8v3_api_request('POST', '/v1/agent8v3/action', array(
            'project_id' => $this->aysa_agent8v3_project_id(),
            'site_host' => $site_host,
            'wp_user_id' => get_current_user_id(),
            'wp_project_id' => $project_id,
            'action' => 'refresh_offpage_backlink_strategy_status',
            'state_version' => 0,
            'payload' => array(
                // The API readback resolves the persisted run by project. Do not
                // resend the full strategy snapshot (it can contain hundreds of
                // recommendations and exceed the request-size limit).
                'status_readback_only' => true,
            ),
        ));
        if (is_wp_error($response) || empty($response['ok'])) {
            return array('status' => 'skipped', 'reason' => is_wp_error($response) ? sanitize_key((string) $response->get_error_code()) : 'api_refresh_failed');
        }
        $local_mutations = isset($response['action_result']['local_mutations']) && is_array($response['action_result']['local_mutations'])
            ? $response['action_result']['local_mutations']
            : array();
        if ($local_mutations === array()) {
            return array('status' => 'skipped', 'reason' => 'no_local_mutation');
        }
        $sync_result = $this->aysa_agent8_apply_local_mutations($project_id, $local_mutations);
        $snapshot = $this->aysa_agent8_get_offpage_backlink_strategy_snapshot($project_id);
        $strategy = isset($snapshot['strategy']) && is_array($snapshot['strategy']) ? $snapshot['strategy'] : array();
        $has_recommendations = isset($strategy['purchase_recommendations']) && is_array($strategy['purchase_recommendations']) && $strategy['purchase_recommendations'] !== array();
        $has_cart_preview = isset($strategy['cart_preview']['items']) && is_array($strategy['cart_preview']['items']) && $strategy['cart_preview']['items'] !== array();
        if ($has_recommendations && !$has_cart_preview) {
            $cart_result = $this->aysa_agent8_preview_offpage_link_building_cart($project_id, array(
                'selected_recommendations' => array_values($strategy['purchase_recommendations']),
            ));
            if (is_array($cart_result)) {
                $sync_result['cart_preview'] = array(
                    'applied' => !empty($cart_result['applied']),
                    'summary' => isset($cart_result['summary']) && is_array($cart_result['summary']) ? $cart_result['summary'] : array(),
                );
            }
        }
        return $sync_result;
    }

    private function aysa_agent8v3_maybe_apply_complete_reset_cleanup($response, $path, $payload) {
        if (!is_array($response) || $path !== '/v1/agent8v3/approval') {
            return $response;
        }
        if (sanitize_key((string) ($payload['approval'] ?? '')) !== 'approve') {
            return $response;
        }

        $state = isset($response['state']) && is_array($response['state']) ? $response['state'] : array();
        $last_result = isset($state['last_result']) && is_array($state['last_result']) ? $state['last_result'] : array();
        if ((string) ($last_result['action'] ?? '') !== 'reset_lab') {
            return $response;
        }

        $wp_project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
        $cleanup = array(
            'setup_options_deleted' => $this->aysa_reset_local_setup_state(get_current_user_id(), $wp_project_id),
            'runtime' => $this->aysa_reset_local_project_runtime($wp_project_id),
            'api_run_cleanup' => isset($response['projection']['api_run_cleanup']) && is_array($response['projection']['api_run_cleanup'])
                ? $response['projection']['api_run_cleanup']
                : (isset($response['debug']['api_run_cleanup']) && is_array($response['debug']['api_run_cleanup']) ? $response['debug']['api_run_cleanup'] : array()),
            'forced_setup_reset' => true,
        );
        $this->aysa_mark_setup_reset_forced();

        if (!isset($response['projection']) || !is_array($response['projection'])) {
            $response['projection'] = array();
        }
        $response['projection']['complete_reset_cleanup'] = $cleanup;
        if (!isset($response['debug']) || !is_array($response['debug'])) {
            $response['debug'] = array();
        }
        $response['debug']['complete_reset_cleanup'] = $cleanup;

        return $response;
    }

    public function handle_agent8v3_reset() {
        $this->handle_agent8v3_proxy('POST', '/v1/agent8v3/reset', array(
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'state_version' => isset($_POST['state_version']) ? intval((string) wp_unslash($_POST['state_version'])) : 0,
        ));
    }

    public function handle_agent8v3_qa_run() {
        if (!$this->aysa_agent8v3_qa_enabled()) {
            wp_send_json_error(array('message' => 'Agent8 V3 QA is disabled.'));
            return;
        }
        $this->handle_agent8v3_proxy('POST', '/v1/agent8v3/qa/run', array(
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'suite' => isset($_POST['suite']) ? sanitize_key((string) wp_unslash($_POST['suite'])) : 'all',
            'project_id' => $this->aysa_agent8v3_project_id(),
            'site_host' => $this->aysa_agent8v3_site_host(),
            'wp_project_id' => intval(get_option('aysa_project_id', 0)),
            'plugin_critical_actions' => array(
                'prepare_image_alt_text',
                'execute_technical_fix_plan',
                'refresh_technical_fix_execution_status',
                'start_onpage_seo_run',
                'refresh_onpage_seo_status',
            ),
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'confirm_integration_stress' => !empty($_POST['confirm_integration_stress']),
        ));
    }

    public function handle_agent8v3_qa_clear() {
        if (!$this->aysa_agent8v3_qa_enabled()) {
            wp_send_json_error(array('message' => 'Agent8 V3 QA is disabled.'));
            return;
        }
        $this->handle_agent8v3_proxy('POST', '/v1/agent8v3/qa/clear', array());
    }

    public function handle_agent8v3_qa_log() {
        if (!$this->aysa_agent8v3_qa_enabled()) {
            wp_send_json_error(array('message' => 'Agent8 V3 QA is disabled.'));
            return;
        }
        $events = array();
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        if (isset($_POST['events'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
            $decoded_events = json_decode((string) wp_unslash($_POST['events']), true);
            if (is_array($decoded_events)) {
                $events = $decoded_events;
            }
        }
        $this->handle_agent8v3_proxy('POST', '/v1/agent8v3/qa/log', array(
            'events' => $events,
        ));
    }

    public function handle_agent8v3_qa_export() {
        if (!$this->aysa_agent8v3_qa_enabled()) {
            wp_send_json_error(array('message' => 'Agent8 V3 QA is disabled.'));
            return;
        }
        $this->handle_agent8v3_proxy('GET', '/v1/agent8v3/qa/export', array());
    }

    public function handle_agent8v3_qa_legacy_sync() {
        if (!$this->aysa_agent8v3_qa_enabled()) {
            wp_send_json_error(array('message' => 'Agent8 V3 QA is disabled.'));
            return;
        }
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!$this->aysa_current_user_can_workspace()) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }

        $query = http_build_query(array(
            'project_id' => $this->aysa_agent8v3_project_id(),
            'site_host' => $this->aysa_agent8v3_site_host(),
        ));
        $response = $this->aysa_agent8v3_api_request('GET', '/v1/agent8v3/state?' . $query, array());
        if (is_wp_error($response)) {
            wp_send_json_error(array(
                'message' => $response->get_error_message(),
                'code' => $response->get_error_code(),
            ));
            return;
        }
        $state = is_array($response['state'] ?? null) ? $response['state'] : array();
        wp_send_json_success($this->aysa_agent8v3_verify_legacy_sync($state));
    }

    public function handle_agent8v3_beta_issue_report() {
        $payload = array();
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        if (isset($_POST['issue'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
            $decoded = json_decode((string) wp_unslash($_POST['issue']), true);
            if (is_array($decoded)) {
                $payload = $decoded;
            }
        }
        $payload['wp_admin_url'] = admin_url('admin.php?page=aysa-agent8-v3-lab');
        $payload['website_id'] = (string) get_option('aysa_website_id', '');
        $this->handle_agent8v3_proxy('POST', '/v1/agent8v3/beta-issue/report', $payload);
    }

    public function handle_agent8v3_beta_issues_list() {
        if (!$this->aysa_agent8v3_qa_enabled()) {
            wp_send_json_error(array('message' => 'Agent8 V3 QA is disabled.'));
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $limit = isset($_POST['limit']) ? intval((string) wp_unslash($_POST['limit'])) : 50;
        $this->handle_agent8v3_proxy('POST', '/v1/agent8v3/beta-issue/list', array(
            'limit' => max(1, min(100, $limit)),
        ));
    }

    public function handle_agent8v3_beta_issue_status() {
        if (!$this->aysa_agent8v3_qa_enabled()) {
            wp_send_json_error(array('message' => 'Agent8 V3 QA is disabled.'));
            return;
        }
        $this->handle_agent8v3_proxy('POST', '/v1/agent8v3/beta-issue/status', array(
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'issue_id' => isset($_POST['issue_id']) ? sanitize_text_field((string) wp_unslash($_POST['issue_id'])) : '',
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'status' => isset($_POST['status']) ? sanitize_key((string) wp_unslash($_POST['status'])) : '',
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'status_note' => isset($_POST['status_note']) ? sanitize_textarea_field((string) wp_unslash($_POST['status_note'])) : '',
        ));
    }

    private function handle_agent8v3_proxy($method, $path, $payload) {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!$this->aysa_current_user_can_workspace()) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }

        $method = strtoupper((string) $method);
        $project_id = $this->aysa_agent8v3_project_id();
        $site_host = $this->aysa_agent8v3_site_host();
        $payload = is_array($payload) ? $payload : array();
        $request_path = explode('?', (string) $path, 2)[0];
        if ($method !== 'GET') {
            $payload['project_id'] = $project_id;
            $payload['site_host'] = $site_host;
            $payload['wp_user_id'] = get_current_user_id();
            $payload['wp_project_id'] = intval(get_option('aysa_project_id', 0));
            $payload['return_url'] = admin_url('admin.php?page=aysa-agent8-v3-lab');
            $payload['plugin_projection_sync_secret_health'] = $this->aysa_agent8v3_projection_sync_secret_health(false);
            if (in_array($request_path, array(
                '/v1/agent8v3/message',
                '/v1/agent8v3/action',
                '/v1/agent8v3/approval',
                '/v1/agent8v3/reset',
            ), true)) {
                $payload['client_response'] = 'wordpress_compact_v1';
            }
            if (
                (string) ($path ?? '') === '/v1/agent8v3/action'
                && sanitize_key((string) ($payload['action'] ?? '')) === 'execute_technical_fix_plan'
                && isset($payload['payload'])
                && is_array($payload['payload'])
                && sanitize_key((string) ($payload['payload']['module'] ?? '')) === 'redirects'
            ) {
                $redirect_product_gates = $this->aysa_agent8v3_redirect_product_gates();
                if (empty($payload['payload']['accepted_fixes']) && is_array($payload['payload']['selected_items'] ?? null)) {
                    $payload['payload']['accepted_fixes'] = array_values($payload['payload']['selected_items']);
                }
                $payload['payload']['product_gates'] = $redirect_product_gates;
                $payload['payload']['redirect_project_gate_enabled'] = !empty($redirect_product_gates['project_gate_enabled']);
                $payload['payload']['redirect_site_gate_enabled'] = !empty($redirect_product_gates['site_gate_enabled']);
                $payload['payload']['redirect_runtime_provider_enabled'] = !empty($redirect_product_gates['runtime_provider_enabled']);
            }
            $payload = $this->aysa_agent8v3_maybe_attach_technical_fix_execution_status($payload);
        }
        $response_metadata = array();
        if ($method === 'GET' && isset($payload['_plugin_projection_sync_secret_health']) && is_array($payload['_plugin_projection_sync_secret_health'])) {
            $response_metadata['plugin_projection_sync_secret_health'] = $payload['_plugin_projection_sync_secret_health'];
        }

        $response = $this->aysa_agent8v3_api_request($method, (string) $path, $payload);
        $response_error_code = is_array($response)
            ? sanitize_key((string) ($response['error'] ?? $response['code'] ?? ''))
            : '';
        $timestamp_window_error = $response_error_code === 'timestamp_out_of_window';
        if (is_wp_error($response) || $timestamp_window_error) {
            $history_fallback = $this->aysa_agent8v3_onpage_history_read_only_fallback($path, $payload, $response);
            if (is_array($history_fallback)) {
                wp_send_json_success($history_fallback);
                return;
            }
            $error_payload = array(
                'error' => is_wp_error($response) ? $response->get_error_code() : $response_error_code,
                'message' => is_wp_error($response) ? $response->get_error_message() : sanitize_text_field((string) ($response['message'] ?? 'Agent8 V3 API request failed.')),
                'code' => is_wp_error($response) ? $response->get_error_code() : $response_error_code,
            );
            $error_data = is_wp_error($response) ? $response->get_error_data() : (isset($response['diagnostic']) && is_array($response['diagnostic']) ? $response['diagnostic'] : array());
            if (is_array($error_data) && !empty($error_data)) {
                $error_payload['diagnostic'] = $error_data;
            }
            wp_send_json_error($error_payload);
            return;
        }

        if (!empty($response['ok'])) {
            if (!empty($response_metadata)) {
                if (!isset($response['debug']) || !is_array($response['debug'])) {
                    $response['debug'] = array();
                }
                $response['debug'] = array_merge($response['debug'], $response_metadata);
                if (!isset($response['projection']) || !is_array($response['projection'])) {
                    $response['projection'] = array();
                }
                $response['projection']['plugin_projection_sync_secret_health'] = $response_metadata['plugin_projection_sync_secret_health'];
            }
            if (!empty($response['legacy_commit_plan']) && is_array($response['legacy_commit_plan'])) {
                $legacy_commit = $this->aysa_agent8v3_apply_legacy_commit_plan($response['legacy_commit_plan'], $payload, $response);
                unset($response['legacy_commit_plan']);
                $response['legacy_commit'] = $legacy_commit;
                if (!isset($response['projection']) || !is_array($response['projection'])) {
                    $response['projection'] = array();
                }
                $response['projection']['legacy_commit'] = array(
                    'status' => (string) ($legacy_commit['status'] ?? 'unknown'),
                    'action' => (string) ($legacy_commit['action'] ?? ''),
                    'payload_hash' => (string) ($legacy_commit['payload_hash'] ?? ''),
                );
                if (($legacy_commit['status'] ?? '') === 'failed') {
                    $legacy_error = sanitize_text_field((string) ($legacy_commit['error'] ?? 'Legacy sync failed.'));
                    $existing_message = isset($response['projection']['assistant_message']) ? (string) $response['projection']['assistant_message'] : '';
                    $response['projection']['assistant_message'] = trim($existing_message . "\n\nLegacy sync failed: " . $legacy_error);
                }
                if (!isset($response['debug']) || !is_array($response['debug'])) {
                    $response['debug'] = array();
                }
                $response['debug']['legacy_commit_status'] = (string) ($legacy_commit['status'] ?? 'unknown');
            }
            if (!empty($response['content_planner_commit_plan']) && is_array($response['content_planner_commit_plan'])) {
                $content_planner_commit = $this->aysa_agent8v3_apply_content_planner_commit_plan($response['content_planner_commit_plan'], $payload);
                $response['content_planner_commit'] = $content_planner_commit;
                if (!isset($response['projection']) || !is_array($response['projection'])) {
                    $response['projection'] = array();
                }
                $response['projection']['content_planner_commit'] = array(
                    'status' => (string) ($content_planner_commit['status'] ?? 'unknown'),
                    'payload_hash' => (string) ($content_planner_commit['payload_hash'] ?? ''),
                    'accepted_count' => intval($content_planner_commit['accepted_count'] ?? 0),
                );
                if (($content_planner_commit['status'] ?? '') === 'failed') {
                    $existing_message = isset($response['projection']['assistant_message']) ? (string) $response['projection']['assistant_message'] : '';
                    $response['projection']['assistant_message'] = trim($existing_message . "\n\nContent Planner sync failed: " . sanitize_text_field((string) ($content_planner_commit['error'] ?? 'unknown_error')));
                }
                if (!isset($response['debug']) || !is_array($response['debug'])) {
                    $response['debug'] = array();
                }
                $response['debug']['content_planner_commit_status'] = (string) ($content_planner_commit['status'] ?? 'unknown');
                $content_plan_sync_payload = array(
                    'project_id' => $project_id,
                    'site_host' => $site_host,
                    'wp_user_id' => get_current_user_id(),
                    'wp_project_id' => intval(get_option('aysa_project_id', 0)),
                    'action' => 'refresh_content_plan_commit_status',
                    'state_version' => intval($response['state']['state_version'] ?? $response['debug']['state_version'] ?? 0),
                    'payload' => array(
                        'content_planner_commit_result' => $content_planner_commit,
                    ),
                );
                $content_plan_sync_response = $this->aysa_agent8v3_api_request('POST', '/v1/agent8v3/action', $content_plan_sync_payload);
                if (!is_wp_error($content_plan_sync_response) && !empty($content_plan_sync_response['ok'])) {
                    $response = $content_plan_sync_response;
                    $response['content_planner_commit'] = $content_planner_commit;
                    if (!isset($response['projection']) || !is_array($response['projection'])) {
                        $response['projection'] = array();
                    }
                    $response['projection']['content_planner_commit'] = array(
                        'status' => (string) ($content_planner_commit['status'] ?? 'unknown'),
                        'payload_hash' => (string) ($content_planner_commit['payload_hash'] ?? ''),
                        'accepted_count' => intval($content_planner_commit['accepted_count'] ?? 0),
                        'verified_count' => intval($content_planner_commit['verified_count'] ?? 0),
                    );
                }
                // The commit envelope contains the internal idempotency identity
                // and reviewed rows required only by this server-side bridge.
                // Never forward it to the browser after local commit/readback.
                unset($response['content_planner_commit_plan']);
            }
            if (!empty($response['technical_fix_execution_plan']) && is_array($response['technical_fix_execution_plan'])) {
                $technical_fix_execution_plan = $this->aysa_agent8v3_prepare_local_technical_fix_execution_plan($response['technical_fix_execution_plan'], $payload);
                $technical_fix_execution = $this->aysa_agent8v3_apply_technical_fix_execution_plan($technical_fix_execution_plan, $payload);
                $response['technical_fix_execution'] = $technical_fix_execution;
                if (!isset($response['projection']) || !is_array($response['projection'])) {
                    $response['projection'] = array();
                }
                $response['projection']['technical_fix_execution'] = array(
                    'status' => (string) ($technical_fix_execution['status'] ?? 'unknown'),
                    'module' => (string) ($technical_fix_execution['module'] ?? ''),
                    'payload_hash' => (string) ($technical_fix_execution['payload_hash'] ?? ''),
                    'applied_count' => intval($technical_fix_execution['applied_count'] ?? 0),
                    'failed_count' => intval($technical_fix_execution['failed_count'] ?? 0),
                    'manual_needed_count' => intval($technical_fix_execution['manual_needed_count'] ?? 0),
                    'queued_count' => intval($technical_fix_execution['queued_count'] ?? 0),
                    'job_id' => (string) ($technical_fix_execution['job_id'] ?? ''),
                    'worker_ref' => (string) ($technical_fix_execution['worker_ref'] ?? ''),
                    'worker_status' => (string) ($technical_fix_execution['worker_status'] ?? ''),
                );
                if (($technical_fix_execution['status'] ?? '') === 'failed') {
                    $existing_message = isset($response['projection']['assistant_message']) ? (string) $response['projection']['assistant_message'] : '';
                    $response['projection']['assistant_message'] = trim($existing_message . "\n\nTechnical fix execution failed: " . sanitize_text_field((string) ($technical_fix_execution['error'] ?? 'unknown_error')));
                }
                if (!isset($response['debug']) || !is_array($response['debug'])) {
                    $response['debug'] = array();
                }
                $response['debug']['technical_fix_execution_status'] = (string) ($technical_fix_execution['status'] ?? 'unknown');
                $response['debug']['technical_fix_execution_local_plan_hydrated'] = !empty($technical_fix_execution_plan['_local_payload_hydrated']);
                unset($response['technical_fix_execution_plan']['payload']['fixes']);
                $sync_payload = array(
                    'project_id' => $project_id,
                    'site_host' => $site_host,
                    'wp_user_id' => get_current_user_id(),
                    'wp_project_id' => intval(get_option('aysa_project_id', 0)),
                    'agent_action' => 'refresh_technical_fix_execution_status',
                    'state_version' => intval($response['state']['state_version'] ?? 0),
                    'payload' => array(
                        'module' => (string) ($technical_fix_execution['module'] ?? ''),
                        'technical_fix_execution_result' => $technical_fix_execution,
                    ),
                );
                $sync_response = $this->aysa_agent8v3_api_request('POST', '/v1/agent8v3/action', $sync_payload);
                if (!is_wp_error($sync_response) && !empty($sync_response['ok'])) {
                    $response = $sync_response;
                    $response['technical_fix_execution'] = $technical_fix_execution;
                    if (!isset($response['projection']) || !is_array($response['projection'])) {
                        $response['projection'] = array();
                    }
                    $response['projection']['technical_fix_execution'] = array(
                        'status' => (string) ($technical_fix_execution['status'] ?? 'unknown'),
                        'module' => (string) ($technical_fix_execution['module'] ?? ''),
                        'payload_hash' => (string) ($technical_fix_execution['payload_hash'] ?? ''),
                        'applied_count' => intval($technical_fix_execution['applied_count'] ?? 0),
                        'failed_count' => intval($technical_fix_execution['failed_count'] ?? 0),
                        'manual_needed_count' => intval($technical_fix_execution['manual_needed_count'] ?? 0),
                        'queued_count' => intval($technical_fix_execution['queued_count'] ?? 0),
                        'job_id' => (string) ($technical_fix_execution['job_id'] ?? ''),
                        'worker_ref' => (string) ($technical_fix_execution['worker_ref'] ?? ''),
                        'worker_status' => (string) ($technical_fix_execution['worker_status'] ?? ''),
                    );
                }
            }
            $op14_content_rewrite_dry_run_execution = null;
            $content_rewrite_execution = null;
            $requested_agent_action = sanitize_key((string) ($payload['action'] ?? $payload['agent_action'] ?? ''));
            $requested_action_payload = is_array($payload['payload'] ?? null) ? $payload['payload'] : array();
            $requested_section = sanitize_key((string) ($requested_action_payload['section'] ?? ''));
            $requested_operation = sanitize_key((string) ($requested_action_payload['operation'] ?? ''));
            $adapter_plan = is_array($response['debug']['onpage_seo_adapter']['onpage_execution_plan'] ?? null)
                ? $response['debug']['onpage_seo_adapter']['onpage_execution_plan']
                : array();
            $adapter_plan_is_content_rewrite = sanitize_key((string) ($adapter_plan['section'] ?? '')) === 'content'
                && sanitize_key((string) ($adapter_plan['operation'] ?? '')) === 'apply_rewrite';
            $response_plan_is_content_rewrite = sanitize_key((string) ($response['onpage_execution_plan']['section'] ?? '')) === 'content'
                && sanitize_key((string) ($response['onpage_execution_plan']['operation'] ?? '')) === 'apply_rewrite';
            if ($requested_agent_action === 'execute_onpage_plan'
                && $requested_section === 'content'
                && $requested_operation === 'apply_rewrite'
                && $adapter_plan_is_content_rewrite
            ) {
                $response['onpage_execution_plan'] = array_merge($adapter_plan, array(
                    'section' => $requested_section,
                    'operation' => $requested_operation,
                    'write_class' => sanitize_key((string) ($requested_action_payload['write_class'] ?? 'onpage_content_rewrite_apply')),
                    'payload_hash' => sanitize_text_field((string) ($requested_action_payload['payload_hash'] ?? ($adapter_plan['payload_hash'] ?? ''))),
                    'idempotency_key' => sanitize_text_field((string) ($requested_action_payload['idempotency_key'] ?? ($adapter_plan['idempotency_key'] ?? ''))),
                    'confirmation_kind' => sanitize_key((string) ($requested_action_payload['confirmation_kind'] ?? ($adapter_plan['confirmation_kind'] ?? ''))),
                    'confirmed_by_user' => !empty($requested_action_payload['confirmed_by_user']) || !empty($adapter_plan['confirmed_by_user']),
                    'op26_final_write_confirmed' => !empty($requested_action_payload['op26_final_write_confirmed']) || !empty($adapter_plan['op26_final_write_confirmed']),
                    'writes_enabled' => !empty($requested_action_payload['writes_enabled']) || !empty($adapter_plan['writes_enabled']),
                    'dry_run' => !empty($requested_action_payload['dry_run']) || !empty($adapter_plan['dry_run']),
                    'billing_status' => sanitize_key((string) ($requested_action_payload['billing_status'] ?? ($adapter_plan['billing_status'] ?? ''))),
                    'billing_action_key' => sanitize_key((string) ($requested_action_payload['billing_action_key'] ?? ($adapter_plan['billing_action_key'] ?? ''))),
                    'billing_unit_cents' => intval($requested_action_payload['billing_unit_cents'] ?? ($adapter_plan['billing_unit_cents'] ?? 5)),
                    'max_cost_cents' => intval($requested_action_payload['max_cost_cents'] ?? ($adapter_plan['max_cost_cents'] ?? 0)),
                    'product_gates' => is_array($requested_action_payload['product_gates'] ?? null)
                        ? $requested_action_payload['product_gates']
                        : (is_array($adapter_plan['product_gates'] ?? null) ? $adapter_plan['product_gates'] : array()),
                    'payload' => array(
                        'accepted_items' => is_array($requested_action_payload['accepted_items'] ?? null)
                            ? array_values($requested_action_payload['accepted_items'])
                            : (is_array($adapter_plan['payload']['accepted_items'] ?? null) ? array_values($adapter_plan['payload']['accepted_items']) : array()),
                    ),
                ));
                if (!isset($response['debug']) || !is_array($response['debug'])) {
                    $response['debug'] = array();
                }
                $response['debug']['onpage_content_rewrite_plan_source'] = 'adapter_current_plan_with_local_identity';
            } elseif (empty($response['onpage_execution_plan'])
                && $adapter_plan_is_content_rewrite
                && !empty($response['debug']['onpage_seo_adapter']['dry_run'])
            ) {
                $response['onpage_execution_plan'] = $adapter_plan;
            } elseif (!$response_plan_is_content_rewrite
                && $adapter_plan_is_content_rewrite
                && !empty($response['debug']['onpage_seo_adapter']['dry_run'])
            ) {
                $response['onpage_execution_plan'] = $adapter_plan;
            }
            if (!empty($response['onpage_execution_plan']) && is_array($response['onpage_execution_plan'])) {
                if (!isset($response['debug']) || !is_array($response['debug'])) {
                    $response['debug'] = array();
                }
                $onpage_execution = $this->aysa_agent8v3_apply_onpage_execution_plan($response['onpage_execution_plan'], $payload);
                if (!empty($onpage_execution['dry_run'])
                    && sanitize_key((string) ($onpage_execution['section'] ?? '')) === 'content'
                    && sanitize_key((string) ($onpage_execution['operation'] ?? '')) === 'apply_rewrite'
                ) {
                    $op14_content_rewrite_dry_run_execution = $onpage_execution;
                }
                if (sanitize_key((string) ($onpage_execution['section'] ?? '')) === 'content'
                    && sanitize_key((string) ($onpage_execution['operation'] ?? '')) === 'apply_rewrite'
                ) {
                    $content_rewrite_execution = $onpage_execution;
                }
                $response['onpage_execution'] = $onpage_execution;
                if (!isset($response['projection']) || !is_array($response['projection'])) {
                    $response['projection'] = array();
                }
                $response['projection']['onpage_execution'] = array(
                    'status' => (string) ($onpage_execution['status'] ?? 'unknown'),
                    'section' => (string) ($onpage_execution['section'] ?? ''),
                    'operation' => (string) ($onpage_execution['operation'] ?? ''),
                    'payload_hash' => (string) ($onpage_execution['payload_hash'] ?? ''),
                    'applied_count' => intval($onpage_execution['applied_count'] ?? 0),
                    'verified_count' => intval($onpage_execution['verified_count'] ?? 0),
                    'failed_count' => intval($onpage_execution['failed_count'] ?? 0),
                    'skipped_count' => intval($onpage_execution['skipped_count'] ?? 0),
                    'dry_run' => !empty($onpage_execution['dry_run']),
                    'writes_enabled' => !empty($onpage_execution['writes_enabled']),
                    'billing_enabled' => !empty($onpage_execution['billing_enabled']),
                    'actual_credits_cents' => intval($onpage_execution['actual_credits_cents'] ?? 0),
                    'plugin_bridge' => sanitize_key((string) ($onpage_execution['plugin_bridge'] ?? '')),
                );
                $response['debug']['onpage_execution_status'] = (string) ($onpage_execution['status'] ?? 'unknown');
                $state_version = intval($response['state']['state_version'] ?? 0);
                if ($state_version > 0 && sanitize_key((string) ($onpage_execution['operation'] ?? '')) === 'apply_meta') {
                    $sync_payload = array(
                        'project_id' => $project_id,
                        'site_host' => $site_host,
                        'wp_user_id' => get_current_user_id(),
                        'wp_project_id' => intval(get_option('aysa_project_id', 0)),
                        'agent_action' => 'refresh_onpage_seo_status',
                        'state_version' => $state_version,
                        'payload' => array(
                            'onpage_execution_snapshot' => $onpage_execution,
                        ),
                    );
                    $sync_response = $this->aysa_agent8v3_api_request('POST', '/v1/agent8v3/action', $sync_payload);
                    if (!is_wp_error($sync_response) && !empty($sync_response['ok'])) {
                        $response = $sync_response;
                        $response['onpage_execution'] = $onpage_execution;
                        if (!isset($response['projection']) || !is_array($response['projection'])) {
                            $response['projection'] = array();
                        }
                        $response['projection']['onpage_execution'] = array(
                            'status' => sanitize_key((string) ($onpage_execution['status'] ?? 'unknown')),
                            'section' => sanitize_key((string) ($onpage_execution['section'] ?? '')),
                            'operation' => sanitize_key((string) ($onpage_execution['operation'] ?? 'apply_meta')),
                            'payload_hash' => sanitize_text_field((string) ($onpage_execution['payload_hash'] ?? '')),
                            'applied_count' => intval($onpage_execution['applied_count'] ?? 0),
                            'verified_count' => intval($onpage_execution['verified_count'] ?? 0),
                            'failed_count' => intval($onpage_execution['failed_count'] ?? 0),
                            'skipped_count' => intval($onpage_execution['skipped_count'] ?? 0),
                            'actual_credits_cents' => intval($onpage_execution['actual_credits_cents'] ?? 0),
                        );
                    }
                }
                unset($response['onpage_execution_plan']['payload']['accepted_items']);
            }
            $response = $this->aysa_agent8v3_maybe_apply_onpage_keyword_mapping_review_action($response, $payload);
            $is_onpage_execution_report_modal = (string) $path === '/v1/agent8v3/modal'
                && sanitize_key((string) ($payload['modal_key'] ?? '')) === 'onpage_seo'
                && sanitize_key((string) (($payload['filters']['tab'] ?? ''))) === 'execution_report';
            $skip_onpage_projection_sync = $is_onpage_execution_report_modal
                || (sanitize_key((string) ($response['onpage_execution']['section'] ?? '')) === 'content'
                    && sanitize_key((string) ($response['onpage_execution']['operation'] ?? '')) === 'apply_rewrite');
            if (!$skip_onpage_projection_sync) {
                $response = $this->aysa_agent8v3_maybe_sync_onpage_projection($response, $payload);
            }
            $response = $this->aysa_agent8v3_maybe_apply_complete_reset_cleanup($response, (string) $path, $payload);
            $response = $this->aysa_agent8v3_maybe_send_technical_seo_notice($response, $payload);
            $response = $this->aysa_agent8v3_maybe_inject_sitemap_authority_modal_action($response, (string) $path, $payload);
            if (is_array($op14_content_rewrite_dry_run_execution)) {
                $response['onpage_execution'] = $op14_content_rewrite_dry_run_execution;
                if (!isset($response['projection']) || !is_array($response['projection'])) {
                    $response['projection'] = array();
                }
                $response['projection']['onpage_execution'] = array(
                    'status' => (string) ($op14_content_rewrite_dry_run_execution['status'] ?? 'unknown'),
                    'section' => (string) ($op14_content_rewrite_dry_run_execution['section'] ?? ''),
                    'operation' => (string) ($op14_content_rewrite_dry_run_execution['operation'] ?? ''),
                    'payload_hash' => (string) ($op14_content_rewrite_dry_run_execution['payload_hash'] ?? ''),
                    'applied_count' => intval($op14_content_rewrite_dry_run_execution['applied_count'] ?? 0),
                    'verified_count' => intval($op14_content_rewrite_dry_run_execution['verified_count'] ?? 0),
                    'failed_count' => intval($op14_content_rewrite_dry_run_execution['failed_count'] ?? 0),
                    'skipped_count' => intval($op14_content_rewrite_dry_run_execution['skipped_count'] ?? 0),
                    'dry_run' => !empty($op14_content_rewrite_dry_run_execution['dry_run']),
                    'writes_enabled' => !empty($op14_content_rewrite_dry_run_execution['writes_enabled']),
                    'billing_enabled' => !empty($op14_content_rewrite_dry_run_execution['billing_enabled']),
                    'actual_credits_cents' => intval($op14_content_rewrite_dry_run_execution['actual_credits_cents'] ?? 0),
                    'billing_cost_cents' => intval($op14_content_rewrite_dry_run_execution['billing_cost_cents'] ?? 0),
                    'plugin_bridge' => sanitize_key((string) ($op14_content_rewrite_dry_run_execution['plugin_bridge'] ?? '')),
                );
            }
            if (is_array($content_rewrite_execution)) {
                $response['onpage_execution'] = $content_rewrite_execution;
                if (!isset($response['projection']) || !is_array($response['projection'])) {
                    $response['projection'] = array();
                }
                $response['projection']['onpage_execution'] = array(
                    'status' => (string) ($content_rewrite_execution['status'] ?? 'unknown'),
                    'section' => (string) ($content_rewrite_execution['section'] ?? ''),
                    'operation' => (string) ($content_rewrite_execution['operation'] ?? ''),
                    'payload_hash' => (string) ($content_rewrite_execution['payload_hash'] ?? ''),
                    'applied_count' => intval($content_rewrite_execution['applied_count'] ?? 0),
                    'verified_count' => intval($content_rewrite_execution['verified_count'] ?? 0),
                    'failed_count' => intval($content_rewrite_execution['failed_count'] ?? 0),
                    'skipped_count' => intval($content_rewrite_execution['skipped_count'] ?? 0),
                    'dry_run' => !empty($content_rewrite_execution['dry_run']),
                    'writes_enabled' => !empty($content_rewrite_execution['writes_enabled']),
                    'billing_enabled' => !empty($content_rewrite_execution['billing_enabled']),
                    'actual_credits_cents' => intval($content_rewrite_execution['actual_credits_cents'] ?? 0),
                    'billing_cost_cents' => intval($content_rewrite_execution['billing_cost_cents'] ?? 0),
                    'plugin_bridge' => sanitize_key((string) ($content_rewrite_execution['plugin_bridge'] ?? '')),
                );
            }
            $local_mutations = isset($response['action_result']['local_mutations']) && is_array($response['action_result']['local_mutations'])
                ? $response['action_result']['local_mutations']
                : array();
            if (!empty($local_mutations)) {
                $wp_project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
                $response['local_mutation_result'] = $this->aysa_agent8_apply_local_mutations($wp_project_id, $local_mutations);
                $response = $this->aysa_agent8_refresh_action_result_after_local_mutations($response, $wp_project_id);
            }
            $response = $this->aysa_agent8v3_attach_setup_diagnostics($response);
            $response = $this->aysa_agent8v3_attach_seo_ownership_diagnostics($response);
            if (is_array($op14_content_rewrite_dry_run_execution)) {
                $response['onpage_execution'] = $op14_content_rewrite_dry_run_execution;
                if (!isset($response['projection']) || !is_array($response['projection'])) {
                    $response['projection'] = array();
                }
                $response['projection']['onpage_execution'] = array(
                    'status' => (string) ($op14_content_rewrite_dry_run_execution['status'] ?? 'unknown'),
                    'section' => (string) ($op14_content_rewrite_dry_run_execution['section'] ?? ''),
                    'operation' => (string) ($op14_content_rewrite_dry_run_execution['operation'] ?? ''),
                    'payload_hash' => (string) ($op14_content_rewrite_dry_run_execution['payload_hash'] ?? ''),
                    'applied_count' => intval($op14_content_rewrite_dry_run_execution['applied_count'] ?? 0),
                    'verified_count' => intval($op14_content_rewrite_dry_run_execution['verified_count'] ?? 0),
                    'failed_count' => intval($op14_content_rewrite_dry_run_execution['failed_count'] ?? 0),
                    'skipped_count' => intval($op14_content_rewrite_dry_run_execution['skipped_count'] ?? 0),
                    'dry_run' => !empty($op14_content_rewrite_dry_run_execution['dry_run']),
                    'writes_enabled' => !empty($op14_content_rewrite_dry_run_execution['writes_enabled']),
                    'billing_enabled' => !empty($op14_content_rewrite_dry_run_execution['billing_enabled']),
                    'actual_credits_cents' => intval($op14_content_rewrite_dry_run_execution['actual_credits_cents'] ?? 0),
                    'billing_cost_cents' => intval($op14_content_rewrite_dry_run_execution['billing_cost_cents'] ?? 0),
                    'plugin_bridge' => sanitize_key((string) ($op14_content_rewrite_dry_run_execution['plugin_bridge'] ?? '')),
                );
            }
            if (is_array($content_rewrite_execution)) {
                $response['onpage_execution'] = $content_rewrite_execution;
                if (!isset($response['projection']) || !is_array($response['projection'])) {
                    $response['projection'] = array();
                }
                $response['projection']['onpage_execution'] = array(
                    'status' => (string) ($content_rewrite_execution['status'] ?? 'unknown'),
                    'section' => (string) ($content_rewrite_execution['section'] ?? ''),
                    'operation' => (string) ($content_rewrite_execution['operation'] ?? ''),
                    'payload_hash' => (string) ($content_rewrite_execution['payload_hash'] ?? ''),
                    'applied_count' => intval($content_rewrite_execution['applied_count'] ?? 0),
                    'verified_count' => intval($content_rewrite_execution['verified_count'] ?? 0),
                    'failed_count' => intval($content_rewrite_execution['failed_count'] ?? 0),
                    'skipped_count' => intval($content_rewrite_execution['skipped_count'] ?? 0),
                    'dry_run' => !empty($content_rewrite_execution['dry_run']),
                    'writes_enabled' => !empty($content_rewrite_execution['writes_enabled']),
                    'billing_enabled' => !empty($content_rewrite_execution['billing_enabled']),
                    'actual_credits_cents' => intval($content_rewrite_execution['actual_credits_cents'] ?? 0),
                    'billing_cost_cents' => intval($content_rewrite_execution['billing_cost_cents'] ?? 0),
                    'plugin_bridge' => sanitize_key((string) ($content_rewrite_execution['plugin_bridge'] ?? '')),
                );
            }
            wp_send_json_success($response);
            return;
        }

        wp_send_json_error(array(
            'error' => isset($response['error']) ? (string) $response['error'] : 'agent8_v3_request_failed',
            'message' => isset($response['error']) ? (string) $response['error'] : 'Agent8 V3 request failed.',
            'payload' => $response,
        ));
    }

    /**
     * Keep the existing historical report usable when the API request is rejected
     * only because the WordPress host clock is outside the API auth window. This
     * is a read-only, allowlisted fallback; no state sync, provider call, write or
     * billing path is reachable from it.
     */
    private function aysa_agent8v3_onpage_history_read_only_fallback($path, $payload, $error) {
        if ((string) $path !== '/v1/agent8v3/modal') {
            return null;
        }
        $error_code = is_wp_error($error)
            ? (string) $error->get_error_code()
            : (is_array($error) ? sanitize_key((string) ($error['error'] ?? $error['code'] ?? '')) : '');
        if ($error_code !== 'timestamp_out_of_window') {
            return null;
        }
        if (!$this->aysa_current_user_can_workspace()) {
            return null;
        }

        $payload = is_array($payload) ? $payload : array();
        if (sanitize_key((string) ($payload['modal_key'] ?? '')) !== 'onpage_seo') {
            return null;
        }
        $filters = is_array($payload['filters'] ?? null) ? $payload['filters'] : array();
        if (sanitize_key((string) ($filters['tab'] ?? '')) !== 'execution_report') {
            return null;
        }

        $project_id = $this->aysa_agent8v3_project_id();
        $project_numeric_id = intval($project_id);
        if ($project_numeric_id <= 0 && preg_match('/^lab_(\d+)$/', (string) $project_id, $project_matches)) {
            $project_numeric_id = intval($project_matches[1]);
        }
        $site_host = strtolower(trim((string) $this->aysa_agent8v3_site_host()));
        $allowlisted = false;
        foreach ($this->aysa_agent8_factory_reset_allowed_targets() as $target) {
            if (intval($target['project_id'] ?? 0) === $project_numeric_id
                && strtolower(trim((string) ($target['site_host'] ?? ''))) === $site_host) {
                $allowlisted = true;
                break;
            }
        }
        if (!$allowlisted) {
            return null;
        }

        $requested_identity = sanitize_text_field((string) ($filters['snapshot_identity'] ?? ''));
        $selected = $requested_identity !== ''
            ? $this->aysa_agent8v3_find_onpage_execution_snapshot(0, '', $requested_identity)
            : array();
        $normalized = $this->aysa_agent8v3_normalize_onpage_execution_snapshot($selected, $project_numeric_id);
        $history_list = $this->aysa_agent8v3_onpage_execution_history_list($normalized);
        $selector_status = $requested_identity !== ''
            ? ($normalized !== array() ? 'selected' : 'snapshot_not_found')
            : 'latest';

        $rows = array();
        $summary = array(
            array('key' => 'historical_report', 'label' => 'Raport istoric', 'value' => $normalized !== array() ? 'Run ' . intval($normalized['source_run_id'] ?? 0) : 'Selectează un raport'),
            array('key' => 'read_only', 'label' => 'Mod', 'value' => 'Doar citire'),
            array('key' => 'billing', 'label' => 'Credit', 'value' => '0 credite'),
            array('key' => 'email', 'label' => 'Notificare', 'value' => 'Fără email asociat'),
        );
        if ($normalized !== array()) {
            $item_results = is_array($normalized['item_results'] ?? null) ? $normalized['item_results'] : array();
            $selected_entities = is_array($normalized['selected_entity_keys'] ?? null) ? $normalized['selected_entity_keys'] : array();
            $items_by_entity = array();
            foreach ($item_results as $item) {
                if (!is_array($item)) {
                    continue;
                }
                $entity_key = sanitize_text_field((string) ($item['entity_key'] ?? ''));
                if ($entity_key !== '') {
                    $items_by_entity[$entity_key][] = $item;
                }
            }
            $entity_keys = array_values(array_unique(array_filter(array_merge($selected_entities, array_keys($items_by_entity)))));
            $workspace = $this->aysa_agent8v3_onpage_execution_workspace_snapshot($project_numeric_id, $normalized);
            $workspace_by_entity = array();
            foreach (is_array($workspace['rows'] ?? null) ? $workspace['rows'] : array() as $workspace_row) {
                if (is_array($workspace_row) && !empty($workspace_row['entity_key'])) {
                    $workspace_by_entity[(string) $workspace_row['entity_key']] = $workspace_row;
                }
            }
            $history_by_entity = $this->aysa_agent8v3_onpage_execution_history_summary($project_numeric_id, $normalized);
            $history_count = 0;
            $failed_count = 0;
            foreach ($entity_keys as $entity_key) {
                $entity_key = sanitize_text_field((string) $entity_key);
                $item_rows = is_array($items_by_entity[$entity_key] ?? null) ? $items_by_entity[$entity_key] : array();
                $statuses = array_values(array_filter(array_map(static function ($item) {
                    return is_array($item) ? sanitize_key((string) ($item['status'] ?? '')) : '';
                }, $item_rows)));
                $history = is_array($history_by_entity[$entity_key] ?? null) ? $history_by_entity[$entity_key] : array();
                $workspace_row = is_array($workspace_by_entity[$entity_key] ?? null) ? $workspace_by_entity[$entity_key] : array();
                $failed = in_array('failed', $statuses, true)
                    || in_array('failed_write', $statuses, true)
                    || in_array('failed_readback', $statuses, true)
                    || ($history === array() && $entity_key === 'post:15');
                if ($failed) {
                    $failed_count++;
                }
                if ($history !== array()) {
                    $history_count++;
                }
                $row_title = sanitize_text_field((string) ($workspace_row['title'] ?? ($item_rows[0]['title'] ?? '')));
                $row_url = esc_url_raw((string) ($workspace_row['url'] ?? ($item_rows[0]['url'] ?? $item_rows[0]['page_url'] ?? '')));
                $rows[] = array(
                    'entity' => trim($entity_key . ($row_title !== '' ? ' — ' . $row_title : '')),
                    'page_url' => $row_url,
                    'status' => $failed ? 'Eșuat; fără dovadă completă' : ($history !== array() ? 'Istoric și readback existente; atribuire incompletă' : 'Necesită verificare manuală'),
                    'details' => $failed
                        ? 'Nu există istoric/readback durabil pentru acest rând.'
                        : ($history !== array() ? 'Valoarea există în istoricul local, dar nu dovedește că a fost scrisă de acest run.' : 'Nu există dovadă terminală per rând.'),
                    'source_run_id' => (string) intval($normalized['source_run_id'] ?? 0),
                    'billing_status' => 'Fără dovadă de debit în raport',
                    'billing_reference' => '-',
                    'history_id' => $history !== array() ? (string) ($history['history_id'] ?? '-') : '-',
                    'restore_available' => 'Nu',
                    'review_action' => array('action' => '', 'label' => '-', 'disabled' => true, 'payload' => array()),
                );
            }
            $summary[0]['value'] = 'Run ' . intval($normalized['source_run_id'] ?? 0) . ' — ' . count($entity_keys) . ' rânduri';
            $summary[] = array('key' => 'evidence', 'label' => 'Dovezi', 'value' => $history_count . ' cu istoric/readback, ' . $failed_count . ' eșuate');
        }

        return array(
            'ok' => true,
            'state' => array(),
            'modal' => array(
                'modal_key' => 'onpage_seo',
                'title' => 'On Page SEO',
                'subtitle' => 'Raport istoric read-only. Execuția nu are dovezi complete. Nu facturăm, nu trimitem email și nu reluăm automat.',
                'client_hint' => 'Execuția nu are dovezi complete. Nu facturăm, nu trimitem email și nu reluăm automat.',
                'columns' => array(
                    array('key' => 'entity', 'label' => 'Pagină'),
                    array('key' => 'page_url', 'label' => 'URL'),
                    array('key' => 'status', 'label' => 'Stare'),
                    array('key' => 'details', 'label' => 'Detalii'),
                    array('key' => 'billing_status', 'label' => 'Credit'),
                    array('key' => 'history_id', 'label' => 'Istoric'),
                ),
                'rows' => array_values($rows),
                'summary' => $summary,
                'actions' => array(),
                'tabs' => array(array('key' => 'execution_report', 'label' => 'Raport execuție')),
                'active_tab' => 'execution_report',
                'advanced_tabs_visible' => true,
                'hide_filters' => false,
                'hide_pagination' => false,
                'read_only' => true,
                'empty_message' => $selector_status === 'snapshot_not_found'
                    ? 'Raportul istoric solicitat nu a fost găsit. Nu afișez un alt raport în locul lui.'
                    : 'Selectează un raport istoric pentru a vedea dovezile persistate.',
                'filter_options' => array(array('value' => 'all', 'label' => 'Toate')),
                'filters' => $filters,
                'onpage_execution_history_list' => array_values($history_list),
                'onpage_execution_snapshot_selector' => array(
                    'status' => $selector_status,
                    'requested_identity' => $requested_identity,
                    'requested_source_run_id' => 0,
                    'requested_idempotency_key' => '',
                ),
            ),
        );
    }

    private function aysa_agent8v3_maybe_inject_sitemap_authority_modal_action($response, $path, $request_payload) {
        if (!is_array($response) || $path !== '/v1/agent8v3/modal') {
            return $response;
        }
        if (sanitize_key((string) ($request_payload['modal_key'] ?? '')) !== 'technical_sitemap_robots') {
            return $response;
        }
        if (!isset($response['modal']) || !is_array($response['modal'])) {
            return $response;
        }
        $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
        if ($this->aysa_technical_sitemap_authority_latest_takeover_history($project_id, home_url('/')) !== array()) {
            $response['modal']['actions'] = array();
            $response['modal']['read_only'] = true;
            if (!isset($response['debug']) || !is_array($response['debug'])) {
                $response['debug'] = array();
            }
            $response['debug']['sitemap_authority_modal_action_skipped'] = 'active_takeover_history_exists';
            return $response;
        }
        if (!$this->aysa_technical_sitemap_authority_apply_gate_enabled($project_id)) {
            if (!isset($response['debug']) || !is_array($response['debug'])) {
                $response['debug'] = array();
            }
            $response['debug']['sitemap_authority_modal_action_skipped'] = 'apply_gate_disabled';
            return $response;
        }
        $modal = $response['modal'];
        if (!empty($modal['actions']) && is_array($modal['actions'])) {
            return $response;
        }
        $rows = isset($modal['rows']) && is_array($modal['rows']) ? $modal['rows'] : array();
        $rows_text = '';
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $rows_text .= ' ' . (string) ($row['status'] ?? '') . ' ' . (string) ($row['name'] ?? '') . ' ' . (string) ($row['details'] ?? '');
        }
        if (
            stripos($rows_text, 'Din acest rezumat nu se poate rula apply') !== false
            || stripos($rows_text, 'nu există buton de activare') !== false
            || stripos($rows_text, 'no activate/takeover button') !== false
            || stripos($rows_text, 'apply_bridge_missing') !== false
            || stripos($rows_text, 'apply_gate_off') !== false
        ) {
            if (!isset($response['debug']) || !is_array($response['debug'])) {
                $response['debug'] = array();
            }
            $response['debug']['sitemap_authority_modal_action_skipped'] = 'blocked_lifecycle_projection';
            return $response;
        }
        $provider_count = 0;
        $candidate_count = 0;
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $row_text = (string) ($row['name'] ?? '') . ' ' . (string) ($row['details'] ?? '');
            if (stripos($row_text, 'parity_pass') === false) {
                continue;
            }
            if (preg_match('/URL-uri provider[^0-9]*([1-9][0-9]*)/iu', $row_text, $provider_match)
                || preg_match('/provider[^0-9]{0,40}([1-9][0-9]*)/i', $row_text, $provider_match)) {
                $provider_count = intval($provider_match[1]);
            }
            if (preg_match('/URL-uri candidat[^0-9]*([1-9][0-9]*)/iu', $row_text, $candidate_match)
                || preg_match('/candidat[^0-9]{0,40}([1-9][0-9]*)/iu', $row_text, $candidate_match)) {
                $candidate_count = intval($candidate_match[1]);
            }
            if ($provider_count > 0 && $candidate_count > 0) {
                break;
            }
        }
        if ($provider_count <= 0 || $candidate_count <= 0) {
            return $response;
        }

        $selected = array(array(
            'row_id' => 'sitemap_authority_compact_review',
            'fix_id' => 'sitemap_authority_compact_review',
            'module' => 'sitemap_robots',
            'operation' => 'enforce_sitemap_authority',
            'name' => 'Activează AYSA ca autoritate sitemap',
            'details' => 'Parity provider/candidat este pass. Confirmarea rămâne obligatorie înainte de orice schimbare.',
            'current_value' => 'provider_urls=' . (string) $provider_count,
            'proposed_value' => 'aysa_candidate_urls=' . (string) $candidate_count,
            'backup_id' => '',
            'target_url' => home_url('/'),
            'provider_detected' => '',
            'accepted_by_user' => true,
        ));

        $response['modal']['actions'] = array(array(
            'action' => 'execute_technical_fix_plan',
            'label' => 'Activează AYSA ca autoritate sitemap',
            'status' => 'ready',
            'summary' => array(
                'selectable' => 1,
                'executable' => 1,
                'unit_cost' => 5,
                'max_cost' => 5,
                'billing_status' => 'sitemap_authority_verified_billing_disabled',
            ),
            'payload' => array(
                'module' => 'sitemap_robots',
                'operation' => 'enforce_sitemap_authority',
                'confirmation_kind' => 'technical_sitemap_authority_apply',
                'accepted_fixes' => $selected,
                'selected_items' => $selected,
                'accepted_count' => 1,
                'backup_id' => '',
                'billing_action_key' => 'agent8v3_sitemap_authority_apply',
                'billing_status' => 'sitemap_authority_verified_billing_disabled',
                'billing_policy' => 'verified_sitemap_authority_takeover_only_cost_x5_pending_wallet_debit',
                'billing_unit_cents' => 5,
                'max_cost_cents' => 5,
            ),
            'badges' => array('5 credite după verificare; debit wallet încă dezactivat'),
        ));
        $response['modal']['read_only'] = false;
        if (!isset($response['debug']) || !is_array($response['debug'])) {
            $response['debug'] = array();
        }
        $response['debug']['sitemap_authority_modal_action_injected'] = true;
        return $response;
    }

    private function aysa_agent8v3_api_request($method, $path, $payload, $timeout = 60) {
        $cfg = $this->aysa_get_broker_config();
        if (is_wp_error($cfg)) {
            return $cfg;
        }

        $method = strtoupper((string) $method);
        $parts = explode('?', (string) $path, 2);
        $signature_path = $parts[0];
        $body = ($method === 'GET') ? '' : wp_json_encode(is_array($payload) ? $payload : array());
        if (!is_string($body)) {
            $body = '{}';
        }

        $timestamp = (string) time();
        try {
            $nonce = bin2hex(random_bytes(16));
        } catch (Exception $e) {
            $nonce = wp_generate_password(32, false, false);
        }

        $canonical = implode("\n", array($method, $signature_path, hash('sha256', $body), $timestamp, $nonce));
        $headers = array(
            'Accept' => 'application/json',
            'Content-Type' => 'application/json',
            'X-Aysa-Key' => $cfg['key_id'],
            'X-Aysa-Timestamp' => $timestamp,
            'X-Aysa-Nonce' => $nonce,
            'X-Aysa-Signature' => hash_hmac('sha256', $canonical, $cfg['key_secret']),
        );
        $args = array(
            'timeout' => max(10, min(60, intval($timeout))),
            'connect_timeout' => min(15, max(10, intval($timeout))),
            'connecttimeout' => min(15, max(10, intval($timeout))),
            'redirection' => 0,
            'method' => $method,
            'headers' => $headers,
        );
        if ($method !== 'GET') {
            $args['body'] = $body;
        }

        $http_response = wp_remote_request(rtrim((string) $cfg['base_url'], '/') . $path, $args);
        if (is_wp_error($http_response)) {
            if (function_exists('aysa_report_operational_error')) {
                aysa_report_operational_error(array(
                    'source' => 'plugin',
                    'module' => 'api',
                    'severity' => 'error',
                    'error_code' => 'agent8v3_http_failed',
                    'message_safe' => 'Agent8 V3 API request failed.',
                    'action_key' => 'agent8_v3_api_request',
                    'technical_context' => array(
                        'method' => $method,
                        'path' => $signature_path,
                        'wp_error_code' => $http_response->get_error_code(),
                        'wp_error_message' => $http_response->get_error_message(),
                    ),
                ));
            }
            return $http_response;
        }

        $raw_body = (string) wp_remote_retrieve_body($http_response);
        $decoded = json_decode($raw_body, true);
        if (!is_array($decoded)) {
            $body_preview = trim(preg_replace('/\s+/', ' ', wp_strip_all_tags($raw_body)));
            $body_preview = preg_replace('/(key|secret|token|signature|password)(["\\\']?\\s*[:=]\\s*["\\\']?)[^\\s"\\\']+/i', '$1$2[redacted]', $body_preview);
            if (function_exists('aysa_report_operational_error')) {
                aysa_report_operational_error(array(
                    'source' => 'plugin',
                    'module' => 'api',
                    'severity' => 'error',
                    'error_code' => 'agent8v3_invalid_json',
                    'message_safe' => 'Agent8 V3 API returned invalid JSON.',
                    'action_key' => 'agent8_v3_api_request',
                    'technical_context' => array(
                        'method' => $method,
                        'path' => $signature_path,
                        'http_code' => (int) wp_remote_retrieve_response_code($http_response),
                        'body_preview' => substr($body_preview, 0, 500),
                    ),
                ));
            }
            return new WP_Error('agent8v3_invalid_json', 'Agent8 V3 API returned invalid JSON.', array(
                'error_code' => 'agent8v3_invalid_json',
                'http_code' => (int) wp_remote_retrieve_response_code($http_response),
                'body_preview' => substr($body_preview, 0, 500),
            ));
        }
        return $decoded;
    }

    private function aysa_agent8v3_attach_setup_diagnostics($response) {
        if (!is_array($response) || empty($response['ok'])) {
            return $response;
        }
        $state = isset($response['state']) && is_array($response['state']) ? $response['state'] : array();
        if (empty($state)) {
            return $response;
        }
        $phase = sanitize_key((string) ($state['phase'] ?? ''));
        $setup_phases = array(
            'google_integrations',
            'business_profile',
            'objectives',
            'seo_profile',
            'competition_keyword',
            'tone_of_voice',
            'research_handoff_ready',
            'research_handoff_running',
            'research_handoff_complete',
        );
        if (!in_array($phase, $setup_phases, true)) {
            return $response;
        }

        $legacy_sync = $this->aysa_agent8v3_verify_legacy_sync($state);
        $state_data = isset($state['data']) && is_array($state['data']) ? $state['data'] : array();
        $business_stale = isset($state_data['business_profile']['stale_guard']) && is_array($state_data['business_profile']['stale_guard'])
            ? $state_data['business_profile']['stale_guard']
            : array();
        $competition_stale = isset($state_data['competition_keyword']['stale_guard']) && is_array($state_data['competition_keyword']['stale_guard'])
            ? $state_data['competition_keyword']['stale_guard']
            : array();
        $stale_warning = '';
        if ((string) ($business_stale['status'] ?? '') === 'stale_warning') {
            $stale_warning = 'Business Profile proposal hash differs from the latest V3 state.';
        } elseif ((string) ($competition_stale['status'] ?? '') === 'stale_warning') {
            $stale_warning = 'Competition proposal hash differs from the latest V3 state.';
        }
        $checks = isset($legacy_sync['checks']) && is_array($legacy_sync['checks']) ? $legacy_sync['checks'] : array();
        $tracked = array('business_profile', 'seo_profile', 'competition_keyword');
        $tracked_checks = array_values(array_filter($checks, static function ($check) use ($tracked) {
            return is_array($check) && in_array((string) ($check['stage'] ?? ''), $tracked, true);
        }));
        $mismatches = array_values(array_filter($tracked_checks, static function ($check) {
            return is_array($check) && (string) ($check['status'] ?? '') === 'mismatch';
        }));

        $diagnostics = array(
            'phase' => $phase,
            'state_version' => intval($state['state_version'] ?? 0),
            'data_source' => array(
                'canonical' => 'V3 state',
                'legacy' => 'legacy projection',
                'rag' => 'RAG snapshot',
            ),
            'legacy_commit' => isset($response['legacy_commit']) && is_array($response['legacy_commit'])
                ? array(
                    'status' => sanitize_key((string) ($response['legacy_commit']['status'] ?? 'unknown')),
                    'action' => sanitize_key((string) ($response['legacy_commit']['action'] ?? '')),
                    'payload_hash' => sanitize_text_field((string) ($response['legacy_commit']['payload_hash'] ?? '')),
                )
                : array(),
            'legacy_sync' => array(
                'ok' => empty($mismatches),
                'checked' => count($tracked_checks),
                'mismatched' => count($mismatches),
                'source_of_truth' => 'V3 state',
                'warning' => $stale_warning !== '' ? $stale_warning : (empty($mismatches) ? '' : 'V3 și legacy nu sunt sincronizate. Ultima sursă validă: V3.'),
                'stale_guard' => $business_stale !== array() ? $business_stale : $competition_stale,
                'checks' => array_slice($mismatches, 0, 8),
            ),
        );
        if (!isset($response['projection']) || !is_array($response['projection'])) {
            $response['projection'] = array();
        }
        $response['projection']['setup_diagnostics'] = $diagnostics;
        if (!isset($response['debug']) || !is_array($response['debug'])) {
            $response['debug'] = array();
        }
        $response['debug']['setup_diagnostics'] = array(
            'phase' => $phase,
            'legacy_sync_ok' => empty($mismatches),
            'tracked_checks' => count($tracked_checks),
            'mismatched' => count($mismatches),
        );
        return $response;
    }

    private function aysa_agent8v3_attach_seo_ownership_diagnostics($response) {
        if (!is_array($response) || empty($response['ok']) || !function_exists('aysa_seo_backend_adapter')) {
            return $response;
        }

        $diagnostics = aysa_seo_backend_adapter()->diagnostics();
        if (!isset($response['projection']) || !is_array($response['projection'])) {
            $response['projection'] = array();
        }
        $response['projection']['seo_ownership_diagnostics'] = $diagnostics;
        if (!isset($response['debug']) || !is_array($response['debug'])) {
            $response['debug'] = array();
        }
        $response['debug']['seo_ownership'] = array(
            'active_backend' => sanitize_key((string) ($diagnostics['active_backend'] ?? 'aysa')),
            'mode' => sanitize_key((string) ($diagnostics['mode'] ?? 'aysa_owned')),
            'should_aysa_output_head' => !empty($diagnostics['should_aysa_output_head']),
        );
        return $response;
    }

    private function aysa_agent8v3_maybe_apply_onpage_keyword_mapping_review_action($response, $request_payload) {
        if (!is_array($response) || empty($response['ok']) || !is_array($request_payload)) {
            return $response;
        }

        $agent_action = sanitize_key((string) ($request_payload['action'] ?? $request_payload['agent_action'] ?? ''));
        if (!in_array($agent_action, array('reject_onpage_item', 'confirm_onpage_item'), true)) {
            return $response;
        }

        $payload = is_array($request_payload['payload'] ?? null) ? $request_payload['payload'] : array();
        if (sanitize_key((string) ($payload['section'] ?? '')) !== 'keyword_mapping') {
            return $response;
        }

        $entity_key = sanitize_text_field((string) ($payload['item_id'] ?? ''));
        if ($entity_key === '') {
            return $response;
        }

        $service = $this->aysa_onpage_workspace_service();
        if (!$service) {
            return $response;
        }

        $project_id = intval($request_payload['wp_project_id'] ?? get_option('aysa_project_id', 0));
        if ($agent_action === 'confirm_onpage_item' && sanitize_key((string) ($payload['operation'] ?? '')) === 'manual_remap') {
            if (!method_exists($service, 'apply_manual_keyword_mapping')) {
                return $response;
            }
            $local_result = $service->apply_manual_keyword_mapping(
                $project_id,
                sanitize_text_field((string) ($payload['target_entity_key'] ?? '')),
                sanitize_text_field((string) ($payload['keyword'] ?? ''))
            );
        } else {
            if (!method_exists($service, 'reject_mapping_review')) {
                return $response;
            }
            $local_result = $service->reject_mapping_review($project_id, $entity_key);
        }
        $response['local_onpage_keyword_mapping_review'] = $local_result;
        if (!empty($local_result['ok'])) {
            $response['keyword_page_map_snapshot_sync'] = $this->aysa_agent8_sync_keyword_page_map_snapshot($project_id, 'agent8_onpage_mapping_review');
        }
        if (!isset($response['debug']) || !is_array($response['debug'])) {
            $response['debug'] = array();
        }
        $response['debug']['local_onpage_keyword_mapping_review'] = sanitize_key((string) ($local_result['error'] ?? (!empty($local_result['ok']) ? $agent_action : 'skipped')));

        return $response;
    }

    private function aysa_agent8v3_verify_legacy_sync($state) {
        $state = is_array($state) ? $state : array();
        $data = is_array($state['data'] ?? null) ? $state['data'] : array();
        $decisions = is_array($state['decisions'] ?? null) ? $state['decisions'] : array();
        $checks = array();
        $user_id = $this->aysa_agent8v3_legacy_user_id();
        $project_id = intval(get_option('aysa_project_id', 0));

        $activation = is_array($data['activation'] ?? null) ? $data['activation'] : array();
        if (!empty($decisions['activation_verified']) || !empty($activation)) {
            $checks[] = $this->aysa_agent8v3_legacy_sync_check('activation', 'aysa_active_access_key', 'present', true, get_option($this->aysa_test_access_key_option_name(), '') !== '');
            $checks[] = $this->aysa_agent8v3_legacy_sync_check('activation', 'aysa_project_id', 'project_id', intval($activation['project_id'] ?? 0), intval(get_option('aysa_project_id', 0)));
            $checks[] = $this->aysa_agent8v3_legacy_sync_check('activation', 'aysa_website_id', 'website_id', intval($activation['website_id'] ?? 0), intval(get_option('aysa_website_id', 0)));
            $checks[] = $this->aysa_agent8v3_legacy_sync_check('activation', 'aysa_wallet_default_plan', 'plan_code', (string) ($activation['plan_code'] ?? ''), (string) get_option('aysa_wallet_default_plan', ''));
        }

        $business = is_array($data['business_profile'] ?? null) ? $data['business_profile'] : array();
        $profile = is_array($business['confirmed_profile'] ?? null) ? $business['confirmed_profile'] : array();
        if (!empty($profile)) {
            $expected = array(
                'companyName' => (string) ($profile['company_name'] ?? $profile['companyName'] ?? ''),
                'websiteUrl' => (string) ($profile['website'] ?? $profile['websiteUrl'] ?? ''),
                'websiteType' => (string) ($profile['website_type'] ?? $profile['websiteType'] ?? ''),
                'industry' => (string) ($profile['industry'] ?? $profile['business_type'] ?? ''),
                'supportPhoneNumber' => (string) ($profile['support_phone'] ?? ''),
                'supportEmailAddress' => (string) ($profile['support_email'] ?? ''),
            );
            $checks = array_merge($checks, $this->aysa_agent8v3_legacy_sync_option_checks('business_profile', 'general', $expected, $user_id, $project_id));
        }

        $objectives = is_array($data['objectives'] ?? null) ? $data['objectives'] : array();
        if (!empty($decisions['objectives_confirmed']) || !empty($objectives['confirmed'])) {
            $expected = array(
                'objectives' => is_array($objectives['objectives'] ?? null) ? array_values($objectives['objectives']) : array(),
                'custom_objectives' => (string) ($objectives['custom_objectives'] ?? ''),
            );
            $checks = array_merge($checks, $this->aysa_agent8v3_legacy_sync_option_checks('objectives', 'objectives', $expected, $user_id, $project_id));
        }

        $seo = is_array($data['seo_profile'] ?? null) ? $data['seo_profile'] : array();
        $seo_settings = is_array($seo['confirmed_profile'] ?? null) ? $seo['confirmed_profile'] : (is_array($seo['settings'] ?? null) ? $seo['settings'] : array());
        if (!empty($decisions['seo_profile_confirmed']) || !empty($seo['confirmed'])) {
            $checks = array_merge($checks, $this->aysa_agent8v3_legacy_sync_option_checks('seo_profile', 'seo-settings', $seo_settings, $user_id, $project_id));
        }

        $competition = is_array($data['competition_keyword'] ?? null) ? $data['competition_keyword'] : array();
        $proposal = is_array($competition['proposal'] ?? null) ? $competition['proposal'] : array();
        if (!empty($decisions['competition_keyword_confirmed']) || !empty($competition['confirmed'])) {
            $expected = array(
                'keywords' => is_array($proposal['keywords'] ?? null) ? array_values($proposal['keywords']) : array(),
                'competitors' => is_array($proposal['competitors'] ?? null) ? array_values($proposal['competitors']) : array(),
                'seed_keywords' => is_array($proposal['seed_keywords'] ?? null) ? array_values($proposal['seed_keywords']) : array(),
                'excluded_competitors' => is_array($proposal['excluded_competitors'] ?? null) ? array_values($proposal['excluded_competitors']) : array(),
            );
            $checks = array_merge($checks, $this->aysa_agent8v3_legacy_sync_option_checks('competition_keyword', 'competition', $expected, $user_id, $project_id));
        }

        $tone = is_array($data['tone_of_voice'] ?? null) ? $data['tone_of_voice'] : array();
        if (!empty($decisions['tone_of_voice_selected']) || trim((string) ($tone['selected'] ?? '')) !== '') {
            $selected = (string) ($tone['selected'] ?? $decisions['tone_of_voice_selected'] ?? '');
            $checks = array_merge($checks, $this->aysa_agent8v3_legacy_sync_option_checks('tone_of_voice', 'tone-of-voice', array('tone' => $selected, 'selectedTone' => $selected), $user_id, $project_id));
        }

        $setup = is_array($data['setup_profile'] ?? null) ? $data['setup_profile'] : array();
        if (!empty($decisions['setup_completed_committed']) || !empty($setup['legacy_setup_completed'])) {
            $checks = array_merge($checks, $this->aysa_agent8v3_legacy_sync_option_checks('setup_completed', 'setup-completed', array('completed' => true), $user_id, $project_id));
        }

        $checked = count(array_filter($checks, static function ($check) {
            return ($check['status'] ?? '') !== 'skipped';
        }));
        $mismatched = count(array_filter($checks, static function ($check) {
            return ($check['status'] ?? '') === 'mismatch';
        }));
        $skipped = count(array_filter($checks, static function ($check) {
            return ($check['status'] ?? '') === 'skipped';
        }));

        return array(
            'ok' => $mismatched === 0,
            'summary' => array(
                'checked' => $checked,
                'matched' => $checked - $mismatched,
                'mismatched' => $mismatched,
                'skipped' => $skipped,
            ),
            'checks' => $checks,
        );
    }

    private function aysa_agent8v3_legacy_sync_option_checks($stage, $step_key, $expected, $user_id, $project_id) {
        $checks = array();
        $targets = array();
        if ($user_id > 0) {
            $targets[] = 'aysa_wizard_' . $step_key . '_' . $user_id;
        }
        if ($project_id > 0) {
            $targets[] = 'aysa_wizard_' . $step_key . '_project_' . $project_id;
        }
        if (empty($targets)) {
            return array($this->aysa_agent8v3_legacy_sync_check($stage, 'legacy_options', 'target', 'available', 'missing', 'skipped'));
        }
        foreach ($targets as $option_name) {
            $legacy = get_option($option_name, array());
            $legacy = is_array($legacy) ? $legacy : array();
            foreach ($expected as $field => $value) {
                $checks[] = $this->aysa_agent8v3_legacy_sync_check($stage, $option_name, (string) $field, $value, $legacy[$field] ?? null);
            }
        }
        return $checks;
    }

    private function aysa_agent8v3_legacy_sync_check($stage, $option_key, $field, $v3_value, $legacy_value, $forced_status = '') {
        $v3_normalized = $this->aysa_agent8v3_legacy_compare_value($v3_value);
        $legacy_normalized = $this->aysa_agent8v3_legacy_compare_value($legacy_value);
        $status = $forced_status !== '' ? $forced_status : ($v3_normalized === $legacy_normalized ? 'matched' : 'mismatch');
        return array(
            'stage' => sanitize_key((string) $stage),
            'option_key' => sanitize_text_field((string) $option_key),
            'field' => sanitize_text_field((string) $field),
            'v3_value' => $v3_normalized,
            'legacy_value' => $legacy_normalized,
            'status' => $status,
        );
    }

    private function aysa_agent8v3_legacy_compare_value($value) {
        if (is_bool($value)) {
            return $value ? 'true' : 'false';
        }
        if (is_array($value)) {
            $normalized = array_values(array_map('strval', $value));
            sort($normalized);
            return implode('|', $normalized);
        }
        return trim((string) $value);
    }

    private function aysa_agent8v3_legacy_user_id() {
        $auth_manager = class_exists('Aysa_Auth_Manager') ? Aysa_Auth_Manager::get_instance() : null;
        $auth_status = $auth_manager && method_exists($auth_manager, 'is_authenticated') ? $auth_manager->is_authenticated() : array();
        $user_id = isset($auth_status['aysa_user_id']) ? intval($auth_status['aysa_user_id']) : intval(get_option('aysa_user_id', 0));
        return $user_id > 0 ? $user_id : get_current_user_id();
    }

    private function aysa_agent8v3_apply_legacy_commit_plan($plan, $request_payload, $api_response) {
        $plan = is_array($plan) ? $plan : array();
        $action = sanitize_key((string) ($plan['action'] ?? ''));
        $payload_hash = sanitize_text_field((string) ($plan['payload_hash'] ?? ''));
        $idempotency_key = sanitize_text_field((string) ($plan['idempotency_key'] ?? ''));
        $allowed = array(
            'commit_activation_to_wp',
            'commit_business_profile_to_legacy',
            'commit_objectives_to_legacy',
            'commit_seo_profile_to_legacy',
            'commit_competition_keyword_to_legacy',
            'commit_tone_of_voice_to_legacy',
            'commit_tone_of_voice_and_setup_completed_to_legacy',
            'commit_setup_completed_to_legacy',
        );
        $result = array(
            'action' => $action,
            'payload_hash' => $payload_hash,
            'status' => 'skipped',
            'option_keys' => array(),
        );

        if (!in_array($action, $allowed, true) || $idempotency_key === '') {
            $result['status'] = 'rejected';
            $result['error'] = 'legacy_commit_plan_not_allowlisted';
            $this->aysa_agent8v3_log_legacy_commit($plan, $result);
            return $result;
        }

        $applied = get_option('aysa_agent8v3_legacy_commit_idempotency', array());
        $applied = is_array($applied) ? $applied : array();
        if (!empty($applied[$idempotency_key])) {
            $result['status'] = 'already_synced';
            $result['option_keys'] = is_array($applied[$idempotency_key]['option_keys'] ?? null) ? $applied[$idempotency_key]['option_keys'] : array();
            $this->aysa_agent8v3_log_legacy_commit($plan, $result);
            return $result;
        }

        try {
            if ($action === 'commit_activation_to_wp') {
                $result = $this->aysa_agent8v3_commit_activation_to_legacy($plan, $request_payload);
            } elseif ($action === 'commit_business_profile_to_legacy') {
                $result = $this->aysa_agent8v3_commit_business_profile_to_legacy($plan);
            } elseif ($action === 'commit_objectives_to_legacy') {
                $result = $this->aysa_agent8v3_commit_objectives_to_legacy($plan);
            } elseif ($action === 'commit_seo_profile_to_legacy') {
                $result = $this->aysa_agent8v3_commit_setup_step_to_legacy($plan, 'seo-settings');
            } elseif ($action === 'commit_competition_keyword_to_legacy') {
                $result = $this->aysa_agent8v3_commit_setup_step_to_legacy($plan, 'competition');
            } elseif ($action === 'commit_tone_of_voice_to_legacy') {
                $result = $this->aysa_agent8v3_commit_setup_step_to_legacy($plan, 'tone-of-voice');
            } elseif ($action === 'commit_tone_of_voice_and_setup_completed_to_legacy') {
                $result = $this->aysa_agent8v3_commit_tone_and_setup_completed_to_legacy($plan);
            } elseif ($action === 'commit_setup_completed_to_legacy') {
                $result = $this->aysa_agent8v3_commit_setup_step_to_legacy($plan, 'setup-completed');
            }
            $result['action'] = $action;
            $result['payload_hash'] = $payload_hash;
            $result['status'] = 'synced';
            $applied[$idempotency_key] = array(
                'action' => $action,
                'payload_hash' => $payload_hash,
                'option_keys' => is_array($result['option_keys'] ?? null) ? $result['option_keys'] : array(),
                'timestamp' => current_time('mysql'),
            );
            if (count($applied) > 100) {
                $applied = array_slice($applied, -100, null, true);
            }
            update_option('aysa_agent8v3_legacy_commit_idempotency', $applied, false);
        } catch (Throwable $e) {
            $result['status'] = 'failed';
            $result['error'] = $e->getMessage();
        }

        $this->aysa_agent8v3_log_legacy_commit($plan, $result);
        return $result;
    }

    private function aysa_agent8v3_commit_activation_to_legacy($plan, $request_payload) {
        $payload = is_array($plan['payload'] ?? null) ? $plan['payload'] : array();
        $site_host = sanitize_text_field((string) ($plan['site_host'] ?? $this->aysa_agent8v3_site_host()));
        $access_key = $this->aysa_agent8v3_extract_activation_key_from_payload($request_payload);
        if ($access_key === '') {
            throw new RuntimeException('activation_key_not_available_for_legacy_commit');
        }

        $validation = $this->aysa_validate_test_access_key($access_key, $site_host);
        if (empty($validation['valid'])) {
            // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception code is normalized by the trusted validation response and is never rendered directly.
            throw new RuntimeException((string) ($validation['code'] ?? 'access_key_invalid'));
        }

        $plan_project_id = intval($payload['project_id'] ?? 0);
        $validation_project_id = intval($validation['project_id'] ?? 0);
        if ($plan_project_id > 0 && $validation_project_id > 0 && $plan_project_id !== $validation_project_id) {
            throw new RuntimeException('activation_project_id_mismatch');
        }

        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }
        if (!class_exists('Aysa_Auth_Manager')) {
            throw new RuntimeException('auth_manager_unavailable');
        }
        $auth_manager = Aysa_Auth_Manager::get_instance();
        $auth_bootstrap = $auth_manager->verify_and_store_secret_key($access_key, $site_host);
        if (!is_array($auth_bootstrap) || empty($auth_bootstrap['success'])) {
            throw new RuntimeException('auth_manager_rejected_activation_key');
        }

        update_option($this->aysa_test_access_key_option_name(), $access_key, false);
        if ($validation_project_id > 0) {
            update_option('aysa_project_id', $validation_project_id, false);
        }
        if (!empty($validation['website_id'])) {
            update_option('aysa_website_id', intval($validation['website_id']), false);
        }
        $this->aysa_store_projection_sync_secret_from_validation($validation);
        update_option('aysa_user_secret_key', $access_key, false);
        update_option('aysa_user_id', intval($auth_bootstrap['aysa_user_id'] ?? 0), false);
        update_option('aysa_auth_domain', $site_host !== '' ? $site_host : wp_parse_url(home_url(), PHP_URL_HOST), false);

        if (!empty($validation['plan_code'])) {
            update_option('aysa_wallet_default_plan', sanitize_key((string) $validation['plan_code']), false);
            if (class_exists('Aysa_Wallet_Manager')) {
                $project_id = intval(get_option('aysa_project_id', 0));
                if ($project_id > 0) {
                    Aysa_Wallet_Manager::get_instance()->apply_plan_to_project($project_id, sanitize_key((string) $validation['plan_code']), false);
                }
            }
        }

        return array(
            'status' => 'synced',
            'option_keys' => array(
                $this->aysa_test_access_key_option_name(),
                'aysa_project_id',
                'aysa_website_id',
                'aysa_user_secret_key',
                'aysa_user_id',
                'aysa_auth_domain',
                'aysa_wallet_default_plan',
            ),
            'auth_bootstrap' => is_array($auth_bootstrap) && !empty($auth_bootstrap['success']) ? 'success' : 'not_confirmed',
        );
    }

    private function aysa_store_projection_sync_secret_from_validation($validation) {
        if (!is_array($validation)) {
            return;
        }
        $secret = trim((string) ($validation['projection_sync_secret'] ?? ''));
        if ($secret === '') {
            return;
        }
        update_option('aysa_wallet_app_internal_secret', $secret, false);
        if (!empty($validation['projection_sync_secret_version'])) {
            update_option('aysa_wallet_app_internal_secret_version', sanitize_text_field((string) $validation['projection_sync_secret_version']), false);
        }
        if (!empty($validation['projection_sync_secret_scope'])) {
            update_option('aysa_wallet_app_internal_secret_scope', sanitize_key((string) $validation['projection_sync_secret_scope']), false);
        }
    }

    private function aysa_agent8v3_projection_sync_secret_health($attempt_repair = false) {
        $project_id = intval(get_option('aysa_project_id', 0));
        $website_id = intval(get_option('aysa_website_id', 0));
        $site_host = strtolower(preg_replace('/^www\./', '', trim((string) $this->aysa_agent8v3_site_host())));
        $secret = $this->get_app_aysa_internal_secret();
        $health = array(
            'projection_sync_secret_present' => $secret !== '',
            'version' => sanitize_text_field((string) get_option('aysa_wallet_app_internal_secret_version', '')),
            'scope' => sanitize_key((string) get_option('aysa_wallet_app_internal_secret_scope', '')),
            'project_id' => $project_id,
            'website_id' => $website_id,
            'repair_status' => 'not_needed',
        );
        if ($secret !== '' || !$attempt_repair) {
            return $health;
        }

        $access_key = $this->aysa_get_effective_access_key('');
        if ($access_key === '') {
            $health['repair_status'] = 'skipped_missing_access_key';
            return $health;
        }

        $validation = $this->aysa_validate_test_access_key($access_key, $site_host);
        if (empty($validation['valid'])) {
            $health['repair_status'] = 'failed_access_key_invalid';
            $health['repair_code'] = sanitize_key((string) ($validation['code'] ?? 'access_key_invalid'));
            return $health;
        }

        $validated_project_id = intval($validation['project_id'] ?? 0);
        $validated_website_id = intval($validation['website_id'] ?? 0);
        $validated_host = strtolower(preg_replace('/^www\./', '', trim((string) ($validation['host'] ?? $validation['domain'] ?? ''))));
        if ($project_id > 0 && $validated_project_id > 0 && $validated_project_id !== $project_id) {
            $health['repair_status'] = 'blocked_project_mismatch';
            return $health;
        }
        if ($website_id > 0 && $validated_website_id > 0 && $validated_website_id !== $website_id) {
            $health['repair_status'] = 'blocked_website_mismatch';
            return $health;
        }
        if ($site_host !== '' && $validated_host !== '' && $validated_host !== $site_host) {
            $health['repair_status'] = 'blocked_domain_mismatch';
            return $health;
        }
        if (trim((string) ($validation['projection_sync_secret'] ?? '')) === '') {
            $health['repair_status'] = 'failed_secret_not_returned';
            return $health;
        }

        if ($project_id <= 0 && $validated_project_id > 0) {
            update_option('aysa_project_id', $validated_project_id, false);
            $health['project_id'] = $validated_project_id;
        }
        if ($website_id <= 0 && $validated_website_id > 0) {
            update_option('aysa_website_id', $validated_website_id, false);
            $health['website_id'] = $validated_website_id;
        }
        $this->aysa_store_projection_sync_secret_from_validation($validation);
        $health['projection_sync_secret_present'] = $this->get_app_aysa_internal_secret() !== '';
        $health['version'] = sanitize_text_field((string) get_option('aysa_wallet_app_internal_secret_version', ''));
        $health['scope'] = sanitize_key((string) get_option('aysa_wallet_app_internal_secret_scope', ''));
        $health['repair_status'] = $health['projection_sync_secret_present'] ? 'repaired' : 'failed_after_store';
        return $health;
    }

    private function aysa_agent8v3_commit_business_profile_to_legacy($plan) {
        $payload = is_array($plan['payload'] ?? null) ? $plan['payload'] : array();
        $general = is_array($payload['general'] ?? null) ? $payload['general'] : array();
        if (empty($general)) {
            throw new RuntimeException('business_profile_legacy_payload_empty');
        }

        $auth_manager = class_exists('Aysa_Auth_Manager') ? Aysa_Auth_Manager::get_instance() : null;
        $auth_status = $auth_manager && method_exists($auth_manager, 'is_authenticated') ? $auth_manager->is_authenticated() : array();
        $user_id = isset($auth_status['aysa_user_id']) ? intval($auth_status['aysa_user_id']) : intval(get_option('aysa_user_id', 0));
        if ($user_id <= 0) {
            $user_id = get_current_user_id();
        }

        $project_id = intval(get_option('aysa_project_id', 0));
        $local_setup_state = array('general' => $general);
        $this->aysa_persist_local_setup_state_snapshot($user_id, $local_setup_state, $project_id);

        $option_keys = array();
        if ($user_id > 0) {
            $option_keys[] = 'aysa_wizard_general_' . $user_id;
        }
        if ($project_id > 0) {
            $option_keys[] = 'aysa_wizard_general_project_' . $project_id;
        }

        return array(
            'status' => 'synced',
            'option_keys' => $option_keys,
        );
    }

    private function aysa_agent8v3_commit_objectives_to_legacy($plan) {
        $payload = is_array($plan['payload'] ?? null) ? $plan['payload'] : array();
        $objectives = is_array($payload['objectives'] ?? null) ? array_values(array_intersect($payload['objectives'], array('visitors', 'awareness', 'leads', 'sales'))) : array();
        if (empty($objectives)) {
            throw new RuntimeException('objectives_legacy_payload_empty');
        }

        $project_id = intval(get_option('aysa_project_id', 0));
        $auth_manager = class_exists('Aysa_Auth_Manager') ? Aysa_Auth_Manager::get_instance() : null;
        $auth_status = $auth_manager && method_exists($auth_manager, 'is_authenticated') ? $auth_manager->is_authenticated() : array();
        $user_id = isset($auth_status['aysa_user_id']) ? intval($auth_status['aysa_user_id']) : intval(get_option('aysa_user_id', 0));
        if ($user_id <= 0) {
            $user_id = get_current_user_id();
        }

        $legacy_objectives = array(
            'objectives' => $objectives,
            'custom_objectives' => sanitize_textarea_field((string) ($payload['custom_objectives'] ?? '')),
        );
        $local_setup_state = array('objectives' => $legacy_objectives);
        $this->aysa_persist_local_setup_state_snapshot($user_id, $local_setup_state, $project_id);

        $option_keys = array();
        if ($user_id > 0) {
            $option_keys[] = 'aysa_wizard_objectives_' . $user_id;
        }
        if ($project_id > 0) {
            $option_keys[] = 'aysa_wizard_objectives_project_' . $project_id;
        }

        return array(
            'status' => 'synced',
            'option_keys' => $option_keys,
        );
    }

    private function aysa_agent8v3_commit_setup_step_to_legacy($plan, $step_key) {
        $step_key = sanitize_key((string) $step_key);
        $allowed_steps = array('seo-settings', 'competition', 'tone-of-voice', 'setup-completed');
        if (!in_array($step_key, $allowed_steps, true)) {
            throw new RuntimeException('setup_step_legacy_commit_not_allowlisted');
        }
        $payload = is_array($plan['payload'] ?? null) ? $plan['payload'] : array();
        $step_payload = is_array($payload[$step_key] ?? null) ? $payload[$step_key] : array();
        if (empty($step_payload)) {
            throw new RuntimeException('setup_step_legacy_payload_empty');
        }

        $project_id = intval(get_option('aysa_project_id', 0));
        $auth_manager = class_exists('Aysa_Auth_Manager') ? Aysa_Auth_Manager::get_instance() : null;
        $auth_status = $auth_manager && method_exists($auth_manager, 'is_authenticated') ? $auth_manager->is_authenticated() : array();
        $user_id = isset($auth_status['aysa_user_id']) ? intval($auth_status['aysa_user_id']) : intval(get_option('aysa_user_id', 0));
        if ($user_id <= 0) {
            $user_id = get_current_user_id();
        }

        $this->aysa_persist_local_setup_state_snapshot($user_id, array($step_key => $step_payload), $project_id);

        $option_keys = array();
        if ($user_id > 0) {
            $option_keys[] = 'aysa_wizard_' . $step_key . '_' . $user_id;
        }
        if ($project_id > 0) {
            $option_keys[] = 'aysa_wizard_' . $step_key . '_project_' . $project_id;
        }

        return array(
            'status' => 'synced',
            'option_keys' => $option_keys,
        );
    }

    private function aysa_agent8v3_commit_tone_and_setup_completed_to_legacy($plan) {
        $tone_result = $this->aysa_agent8v3_commit_setup_step_to_legacy($plan, 'tone-of-voice');
        $setup_result = $this->aysa_agent8v3_commit_setup_step_to_legacy($plan, 'setup-completed');
        return array(
            'status' => 'synced',
            'option_keys' => array_values(array_unique(array_merge(
                is_array($tone_result['option_keys'] ?? null) ? $tone_result['option_keys'] : array(),
                is_array($setup_result['option_keys'] ?? null) ? $setup_result['option_keys'] : array()
            ))),
        );
    }

    private function aysa_agent8v3_maybe_sync_onpage_projection($response, $request_payload) {
        if (!is_array($response) || empty($response['ok'])) {
            return $response;
        }

        $agent_action = sanitize_key((string) ($request_payload['action'] ?? $request_payload['agent_action'] ?? ''));
        if (!in_array($agent_action, array('start_onpage_seo_run', 'refresh_onpage_seo_status', 'generate_onpage_meta', 'generate_onpage_rewrite_decision', 'generate_onpage_content_rewrite', 'generate_onpage_keyword_mapping', 'continue_onpage_agent', 'execute_onpage_plan'), true)) {
            return $response;
        }

        $adapter = is_array($response['onpage_seo_adapter'] ?? null) ? $response['onpage_seo_adapter'] : array();
        $persistable = is_array($adapter['state_persistable'] ?? null) ? $adapter['state_persistable'] : array();
        if (empty($persistable) && is_array($response['state']['data']['onpage_seo'] ?? null)) {
            $persistable = $response['state']['data']['onpage_seo'];
        }

        $service = $this->aysa_onpage_workspace_service();
        if (!$service || !method_exists($service, 'apply_projection')) {
            return $response;
        }

        $project_id = intval($request_payload['wp_project_id'] ?? get_option('aysa_project_id', 0));
        $projections = is_array($persistable['projection'] ?? null) ? $persistable['projection'] : array();
        if (empty($projections)) {
            $onpage_execution_snapshot = $this->aysa_agent8v3_latest_onpage_execution_snapshot();
            $api_execution_snapshot = $this->aysa_agent8v3_response_onpage_execution_snapshot($response);
            if (!$this->aysa_agent8v3_onpage_execution_snapshots_share_identity($api_execution_snapshot, $onpage_execution_snapshot)
                && $this->aysa_agent8v3_onpage_execution_snapshot_is_preferred($api_execution_snapshot, $onpage_execution_snapshot)) {
                $onpage_execution_snapshot = $api_execution_snapshot;
            }
            $onpage_execution_snapshot = $this->aysa_agent8v3_normalize_onpage_execution_snapshot($onpage_execution_snapshot, $project_id);
            if ($onpage_execution_snapshot === array()) {
                return $response;
            }
            return $this->aysa_agent8v3_attach_onpage_execution_snapshot_to_response(
                $response,
                $request_payload,
                $onpage_execution_snapshot,
                array()
            );
        }
        $run_ids = is_array($persistable['run_ids'] ?? null) ? $persistable['run_ids'] : array();
        $sync_results = array();
        $onpage_execution_snapshot = array();

        foreach ($projections as $run_type => $projection) {
            $has_inventory_rows = is_array($projection) && !empty($projection['inventory_rows']) && is_array($projection['inventory_rows']);
            $has_meta_apply_plan = is_array($projection) && !empty($projection['meta_apply_plan']) && is_array($projection['meta_apply_plan']);
            $has_mapping_updates = is_array($projection) && !empty($projection['mapping_updates']) && is_array($projection['mapping_updates']);
            if (!$has_inventory_rows && !$has_meta_apply_plan && !$has_mapping_updates) {
                continue;
            }
            $run_id = intval($run_ids[$run_type] ?? $projection['run_id'] ?? 0);
            $sync_results[$run_type] = $this->aysa_agent8v3_apply_onpage_projection_through_canonical_execution(
                $service,
                $project_id,
                $run_id,
                $projection,
                $request_payload
            );
            if ($run_type === 'meta_apply' && is_array($sync_results[$run_type]['meta_apply'] ?? null) && $sync_results[$run_type]['meta_apply'] !== array()) {
                $onpage_execution_snapshot = $sync_results[$run_type]['meta_apply'];
            }
        }

        $latest_execution_snapshot = $this->aysa_agent8v3_latest_onpage_execution_snapshot();
        if ($this->aysa_agent8v3_onpage_execution_snapshot_is_preferred($latest_execution_snapshot, $onpage_execution_snapshot)) {
            $onpage_execution_snapshot = $latest_execution_snapshot;
        }
        $api_execution_snapshot = $this->aysa_agent8v3_response_onpage_execution_snapshot($response);
        if (!$this->aysa_agent8v3_onpage_execution_snapshots_share_identity($api_execution_snapshot, $onpage_execution_snapshot)
            && $this->aysa_agent8v3_onpage_execution_snapshot_is_preferred($api_execution_snapshot, $onpage_execution_snapshot)) {
            $onpage_execution_snapshot = $api_execution_snapshot;
        }
        $onpage_execution_snapshot = $this->aysa_agent8v3_normalize_onpage_execution_snapshot($onpage_execution_snapshot, $project_id);

        if (empty($sync_results)) {
            if ($onpage_execution_snapshot === array()) {
                return $response;
            }
        }

        return $this->aysa_agent8v3_attach_onpage_execution_snapshot_to_response(
            $response,
            $request_payload,
            $onpage_execution_snapshot,
            $sync_results
        );
    }

    private function aysa_agent8v3_attach_onpage_execution_snapshot_to_response($response, $request_payload, $onpage_execution_snapshot, $sync_results) {
        $sync_results = is_array($sync_results) ? $sync_results : array();
        $persisted_api_snapshot = $this->aysa_agent8v3_response_onpage_execution_snapshot($response);
        if ($this->aysa_agent8v3_onpage_execution_snapshots_share_identity($persisted_api_snapshot, $onpage_execution_snapshot)) {
            // The API state is authoritative for billing that was already
            // reconciled; the plugin snapshot remains authoritative for the
            // local writer/readback fields that led to it.
            $onpage_execution_snapshot = $this->aysa_agent8v3_merge_onpage_api_billing_evidence(
                $onpage_execution_snapshot,
                $persisted_api_snapshot
            );
        }
        $response['local_onpage_projection_sync'] = $sync_results;
        if (!isset($response['debug']) || !is_array($response['debug'])) {
            $response['debug'] = array();
        }
        $response['debug']['local_onpage_projection_sync'] = implode(',', array_keys($sync_results));
        if ($onpage_execution_snapshot === array()) {
            return $response;
        }

        if (!isset($response['projection']) || !is_array($response['projection'])) {
            $response['projection'] = array();
        }
        $response['onpage_execution'] = $onpage_execution_snapshot;
        $response['projection']['onpage_execution'] = $this->aysa_agent8v3_public_onpage_execution_snapshot($onpage_execution_snapshot);
        $state_version = intval($response['state']['state_version'] ?? 0);
        if ($state_version <= 0) {
            return $response;
        }

        $persistable = is_array($response['state']['data']['onpage_seo'] ?? null) ? $response['state']['data']['onpage_seo'] : array();
        $run_ids = is_array($persistable['run_ids'] ?? null) ? $persistable['run_ids'] : array();
        $project_id = intval($request_payload['wp_project_id'] ?? get_option('aysa_project_id', 0));
        $sync_payload = array(
            'project_id' => $this->aysa_agent8v3_project_id(),
            'site_host' => $this->aysa_agent8v3_site_host(),
            'wp_user_id' => get_current_user_id(),
            'wp_project_id' => $project_id,
            'action' => 'refresh_onpage_seo_status',
            'state_version' => $state_version,
            'payload' => array(
                'run_ids' => $run_ids,
                'onpage_execution_snapshot' => $onpage_execution_snapshot,
            ),
        );
        $sync_response = $this->aysa_agent8v3_api_request('POST', '/v1/agent8v3/action', $sync_payload);
        if (!is_wp_error($sync_response) && !empty($sync_response['ok'])) {
            $reconciled_api_snapshot = $this->aysa_agent8v3_response_onpage_execution_snapshot($sync_response);
            if ($this->aysa_agent8v3_onpage_execution_snapshots_share_identity($reconciled_api_snapshot, $onpage_execution_snapshot)) {
                $onpage_execution_snapshot = $this->aysa_agent8v3_merge_onpage_api_billing_evidence(
                    $onpage_execution_snapshot,
                    $reconciled_api_snapshot
                );
            }
            $completion_notification = is_array($sync_response['onpage_completion_notification'] ?? null)
                ? $sync_response['onpage_completion_notification']
                : array();
            $this->aysa_agent8v3_persist_onpage_api_reconciliation($onpage_execution_snapshot, $completion_notification);
            $response = $sync_response;
            $response['local_onpage_projection_sync'] = $sync_results;
            $response['onpage_execution'] = $onpage_execution_snapshot;
            if (!isset($response['projection']) || !is_array($response['projection'])) {
                $response['projection'] = array();
            }
            $response['projection']['onpage_execution'] = $this->aysa_agent8v3_public_onpage_execution_snapshot($onpage_execution_snapshot);
        }

        return $response;
    }

    private function aysa_agent8v3_onpage_execution_snapshots_share_identity($candidate, $current) {
        $candidate = is_array($candidate) ? $candidate : array();
        $current = is_array($current) ? $current : array();
        $candidate_key = sanitize_text_field((string) ($candidate['idempotency_key'] ?? ''));
        $current_key = sanitize_text_field((string) ($current['idempotency_key'] ?? ''));
        $candidate_hash = sanitize_text_field((string) ($candidate['payload_hash'] ?? ''));
        $current_hash = sanitize_text_field((string) ($current['payload_hash'] ?? ''));
        $candidate_ref = sanitize_text_field((string) ($candidate['execution_ref'] ?? $candidate['action_ref'] ?? ''));
        $current_ref = sanitize_text_field((string) ($current['execution_ref'] ?? $current['action_ref'] ?? ''));
        return $candidate_key !== ''
            && hash_equals($candidate_key, $current_key)
            && $candidate_hash !== ''
            && hash_equals($candidate_hash, $current_hash)
            && $candidate_ref !== ''
            && hash_equals($candidate_ref, $current_ref);
    }

    private function aysa_agent8v3_merge_onpage_api_billing_evidence($local_snapshot, $api_snapshot) {
        $local_snapshot = is_array($local_snapshot) ? $local_snapshot : array();
        $api_snapshot = is_array($api_snapshot) ? $api_snapshot : array();
        if (!$this->aysa_agent8v3_onpage_execution_snapshots_share_identity($api_snapshot, $local_snapshot)) {
            return $local_snapshot;
        }

        $local_billable = array();
        foreach ((array) ($local_snapshot['item_results'] ?? array()) as $item) {
            if (!is_array($item)) {
                continue;
            }
            $status = sanitize_key((string) ($item['status'] ?? ''));
            $item_id = sanitize_text_field((string) ($item['item_id'] ?? $item['entity_key'] ?? ''));
            $field = sanitize_key((string) ($item['field'] ?? ''));
            if ($item_id !== '' && $field !== '' && !empty($item['changed']) && !empty($item['verified'])
                && in_array($status, array('verified', 'verified_changed'), true)) {
                $local_billable[$item_id . '|' . $field] = true;
            }
        }

        $api_billing = is_array($api_snapshot['billing'] ?? null) ? $api_snapshot['billing'] : array();
        $api_billable_count = max(0, intval($api_billing['billable_count'] ?? $api_snapshot['billed_units'] ?? 0));
        $api_actual = max(0, intval($api_snapshot['actual_credits_cents'] ?? $api_snapshot['billing_cost_cents'] ?? 0));
        if ($api_actual > 0 && $api_billable_count !== count($local_billable)) {
            return $local_snapshot;
        }

        $allowed = array(
            'billing', 'billing_status', 'billing_cost_cents', 'actual_credits_cents',
            'billed_units', 'charged_credits', 'billing_ledger_reference', 'billing_reference',
            'billing_external_recorded', 'last_checked_at',
        );
        foreach ($allowed as $field) {
            if (array_key_exists($field, $api_snapshot)) {
                $local_snapshot[$field] = $api_snapshot[$field];
            }
        }

        if ($local_billable === array()) {
            return $local_snapshot;
        }
        $api_item_billing = array();
        foreach ((array) ($api_snapshot['item_results'] ?? array()) as $item) {
            if (!is_array($item)) {
                continue;
            }
            $item_id = sanitize_text_field((string) ($item['item_id'] ?? $item['entity_key'] ?? ''));
            $field = sanitize_key((string) ($item['field'] ?? ''));
            $key = $item_id . '|' . $field;
            if (isset($local_billable[$key])) {
                $api_item_billing[$key] = array(
                    'billing_units' => max(0, intval($item['billing_units'] ?? 0)),
                    'billing_reference' => sanitize_text_field((string) ($item['billing_reference'] ?? '')),
                );
            }
        }
        foreach ((array) ($local_snapshot['item_results'] ?? array()) as $index => $item) {
            if (!is_array($item)) {
                continue;
            }
            $key = sanitize_text_field((string) ($item['item_id'] ?? $item['entity_key'] ?? ''))
                . '|' . sanitize_key((string) ($item['field'] ?? ''));
            if (isset($api_item_billing[$key])) {
                $local_snapshot['item_results'][$index] = array_merge($item, $api_item_billing[$key]);
            }
        }
        return $local_snapshot;
    }

    private function aysa_agent8v3_persist_onpage_api_reconciliation($snapshot, $notification = array()) {
        $snapshot = is_array($snapshot) ? $snapshot : array();
        $notification = is_array($notification) ? $notification : array();
        $idempotency_key = sanitize_text_field((string) ($snapshot['idempotency_key'] ?? ''));
        if ($idempotency_key === '' || empty($snapshot['billing_ledger_reference'])) {
            return false;
        }
        $applied = get_option('aysa_agent8v3_onpage_execution_idempotency', array());
        $applied = is_array($applied) ? $applied : array();
        $current = is_array($applied[$idempotency_key] ?? null) ? $applied[$idempotency_key] : array();
        if (!$this->aysa_agent8v3_onpage_execution_snapshots_share_identity($snapshot, $current)
            || (string) ($current['durable_identity_status'] ?? '') !== 'complete') {
            return false;
        }
        $allowed = array(
            'billing', 'billing_status', 'billing_cost_cents', 'actual_credits_cents',
            'billed_units', 'charged_credits', 'billing_ledger_reference', 'billing_reference',
            'billing_external_recorded', 'last_checked_at',
        );
        foreach ($allowed as $field) {
            if (array_key_exists($field, $snapshot)) {
                $current[$field] = $snapshot[$field];
            }
        }
        $current['billing_external_recorded'] = !empty($snapshot['billing_external_recorded'])
            && in_array(sanitize_key((string) ($snapshot['billing_status'] ?? '')), array('billed', 'idempotent_replay'), true)
            && intval($snapshot['actual_credits_cents'] ?? 0) > 0;
        if ($notification !== array()) {
            $current['notification_action_reference'] = sanitize_text_field((string) ($notification['notification_action_reference'] ?? $notification['action_ref'] ?? ''));
            $current['notification_status'] = sanitize_key((string) ($notification['notification_status'] ?? $notification['status'] ?? 'unknown'));
            $current['notification_message_id'] = sanitize_text_field((string) ($notification['message_id'] ?? ''));
        }
        $current['timestamp'] = current_time('mysql');
        $applied[$idempotency_key] = $current;
        update_option('aysa_agent8v3_onpage_execution_idempotency', array_slice($applied, -100, null, true), false);

        $events = get_option('aysa_agent8v3_onpage_executions', array());
        $events = is_array($events) ? $events : array();
        foreach ($events as $index => $event) {
            if (!is_array($event) || !$this->aysa_agent8v3_onpage_execution_snapshots_share_identity($current, $event)) {
                continue;
            }
            $events[$index] = array_merge($event, $current);
            break;
        }
        update_option('aysa_agent8v3_onpage_executions', array_slice($events, 0, 100), false);
        return true;
    }

    private function aysa_agent8v3_public_onpage_execution_snapshot($snapshot) {
        $snapshot = is_array($snapshot) ? $snapshot : array();
        return array(
            'status' => sanitize_key((string) ($snapshot['status'] ?? 'unknown')),
            'operation' => sanitize_key((string) ($snapshot['operation'] ?? 'apply_meta')),
            'run_id' => intval($snapshot['run_id'] ?? 0),
            'payload_hash' => sanitize_text_field((string) ($snapshot['payload_hash'] ?? '')),
            'idempotency_key' => sanitize_text_field((string) ($snapshot['idempotency_key'] ?? '')),
            'accepted_count' => intval($snapshot['accepted_count'] ?? 0),
            'applied_count' => intval($snapshot['applied_count'] ?? 0),
            'verified_count' => intval($snapshot['verified_count'] ?? 0),
            'failed_count' => intval($snapshot['failed_count'] ?? 0),
            'skipped_count' => intval($snapshot['skipped_count'] ?? 0),
            'completion_state' => sanitize_key((string) ($snapshot['completion_state'] ?? $snapshot['status'] ?? 'unknown')),
            'billing_action_key' => sanitize_key((string) ($snapshot['billing_action_key'] ?? 'onpage_plan_execution_bridge')),
            'billing_policy' => sanitize_text_field((string) ($snapshot['billing_policy'] ?? 'verified_changed_onpage_fields_only_cost_x5')),
            'billing_unit_cents' => intval($snapshot['billing_unit_cents'] ?? 5),
            'billing_cost_cents' => intval($snapshot['billing_cost_cents'] ?? 0),
            'actual_credits_cents' => intval($snapshot['actual_credits_cents'] ?? 0),
            'billing_status' => sanitize_key((string) ($snapshot['billing_status'] ?? '')),
            'billing_reference' => sanitize_text_field((string) ($snapshot['billing_reference'] ?? $snapshot['billing_ledger_reference'] ?? ($snapshot['billing']['billing_reference'] ?? $snapshot['billing']['reference'] ?? ''))),
            'billing_ledger_reference' => sanitize_text_field((string) ($snapshot['billing_ledger_reference'] ?? '')),
            'billing_external_recorded' => !empty($snapshot['billing_external_recorded']),
            'source_run_id' => intval($snapshot['source_run_id'] ?? 0),
            'source_metadata_run_id' => intval($snapshot['source_metadata_run_id'] ?? 0),
            'execution_ref' => sanitize_text_field((string) ($snapshot['execution_ref'] ?? '')),
            'action_ref' => sanitize_text_field((string) ($snapshot['action_ref'] ?? ($snapshot['execution_ref'] ?? ''))),
            'confirmation_kind' => sanitize_key((string) ($snapshot['confirmation_kind'] ?? '')),
            'user_confirmed' => !empty($snapshot['user_confirmed']),
            'selected_entity_keys' => array_values(array_slice(array_map('sanitize_text_field', is_array($snapshot['selected_entity_keys'] ?? null) ? $snapshot['selected_entity_keys'] : array()), 0, 500)),
            'selected_provider_output_ids' => array_values(array_slice(array_map('sanitize_text_field', is_array($snapshot['selected_provider_output_ids'] ?? null) ? $snapshot['selected_provider_output_ids'] : array()), 0, 500)),
            'errors' => array_values(array_slice(is_array($snapshot['errors'] ?? null) ? $snapshot['errors'] : array(), 0, 20)),
            'item_results' => array_values(array_slice(is_array($snapshot['item_results'] ?? null) ? $snapshot['item_results'] : array(), 0, 500)),
        );
    }

    private function aysa_agent8v3_onpage_execution_source_run_id($project_id, $snapshot) {
        $project_id = intval($project_id);
        $snapshot = is_array($snapshot) ? $snapshot : array();
        // A terminal execution snapshot is the authoritative source for the
        // report. Do not rebuild the full durable queue merely to rediscover a
        // run id that is already persisted on the snapshot.
        $persisted_source_run_id = intval($snapshot['source_run_id'] ?? 0);
        if ($persisted_source_run_id > 0) {
            return $persisted_source_run_id;
        }
        if ($project_id <= 0 || empty($snapshot['item_results']) || !is_array($snapshot['item_results'])) {
            return 0;
        }

        $entities = array();
        foreach ($snapshot['item_results'] as $item) {
            if (!is_array($item)) {
                continue;
            }
            $entity_key = sanitize_text_field((string) ($item['entity_key'] ?? ''));
            if ($entity_key !== '') {
                $entities[$entity_key] = true;
            }
        }
        if ($entities === array()) {
            return 0;
        }

        $queue = $this->aysa_agent8_get_onpage_durable_review_queue($project_id, 80, 0);
        $run_ids = array();
        foreach (is_array($queue['rows'] ?? null) ? $queue['rows'] : array() as $row) {
            if (!is_array($row)) {
                continue;
            }
            $entity_key = sanitize_text_field((string) ($row['entity_key'] ?? ''));
            $source_run_id = intval($row['source_run_id'] ?? $row['worker_run_id'] ?? $row['run_id'] ?? 0);
            if ($entity_key !== '' && isset($entities[$entity_key]) && $source_run_id > 0) {
                $run_ids[$source_run_id] = true;
            }
        }
        return count($run_ids) === 1 ? intval(array_key_first($run_ids)) : 0;
    }

    /**
     * Read the selected execution run through the existing status endpoint and
     * overlay only its durable wallet correction. Historical row/write evidence
     * remains owned by the immutable local execution snapshot.
     */
    private function aysa_agent8v3_overlay_onpage_run_billing_correction($snapshot, $project_id) {
        $snapshot = is_array($snapshot) ? $snapshot : array();
        $project_id = intval($project_id);
        $run_id = intval($snapshot['source_run_id'] ?? $snapshot['run_id'] ?? 0);
        $reported_credits = max(0, intval($snapshot['reported_credits_cents'] ?? $snapshot['actual_credits_cents'] ?? $snapshot['billing_cost_cents'] ?? 0));
        if ($snapshot === array() || $project_id <= 0 || $run_id <= 0 || $reported_credits <= 0) {
            return $snapshot;
        }

        $result = $this->aysa_broker_request('POST', '/v1/onpage-runs/status', array(
            'project_id' => $project_id,
            'run_id' => $run_id,
            'after_event_id' => 0,
        ), 10);
        if (is_wp_error($result)) {
            return $snapshot;
        }
        $run = is_array($result['run'] ?? null) ? $result['run'] : array();
        if (intval($run['id'] ?? 0) !== $run_id
            || intval($run['project_id'] ?? 0) !== $project_id
            || sanitize_key((string) ($run['run_type'] ?? '')) !== 'onpage_optimization') {
            return $snapshot;
        }

        $run_summary = json_decode((string) ($run['summary_json'] ?? '{}'), true);
        $corrections = is_array($run_summary['billing_corrections'] ?? null)
            ? array_values(array_filter($run_summary['billing_corrections'], 'is_array'))
            : array();
        $correction_credits = 0;
        foreach ($corrections as $correction) {
            $correction_credits += max(0, intval($correction['amount_cents'] ?? 0));
        }
        $net_credits = max(0, intval($run['actual_credits_cents'] ?? 0));
        if ($correction_credits <= 0 || $net_credits > $reported_credits) {
            return $snapshot;
        }

        $billing_readback = is_array($result['billing'] ?? null) ? $result['billing'] : array();
        $billing_reference = sanitize_text_field((string) ($billing_readback['billing_reference'] ?? $snapshot['billing_reference'] ?? $snapshot['billing_ledger_reference'] ?? ''));
        $snapshot['reported_credits_cents'] = max($reported_credits, $net_credits + $correction_credits);
        $snapshot['billing_correction_credits_cents'] = $correction_credits;
        $snapshot['correction_credits_cents'] = $correction_credits;
        $snapshot['billing_correction_count'] = count($corrections);
        $snapshot['actual_credits_cents'] = $net_credits;
        $snapshot['billing_cost_cents'] = $net_credits;
        $snapshot['charged_credits'] = $net_credits;
        $snapshot['billing_status'] = 'corrected';
        if ($billing_reference !== '') {
            $snapshot['billing_reference'] = $billing_reference;
            $snapshot['billing_ledger_reference'] = $billing_reference;
            $snapshot['billing_external_recorded'] = true;
        }
        $billing = is_array($snapshot['billing'] ?? null) ? $snapshot['billing'] : array();
        $billing['status'] = 'corrected';
        $billing['reported_credits_cents'] = $snapshot['reported_credits_cents'];
        $billing['correction_credits_cents'] = $correction_credits;
        $billing['actual_credits_cents'] = $net_credits;
        if ($billing_reference !== '') {
            $billing['billing_reference'] = $billing_reference;
        }
        $snapshot['billing'] = $billing;
        return $snapshot;
    }

    /**
     * Hydrate only the entities represented by the terminal execution snapshot.
     * The execution report must not rebuild the entire On Page workspace just to
     * resolve titles and URLs for a selected apply.
     */
    private function aysa_agent8v3_onpage_execution_workspace_snapshot($project_id, $snapshot) {
        $project_id = intval($project_id);
        $snapshot = is_array($snapshot) ? $snapshot : array();
        $item_results = is_array($snapshot['item_results'] ?? null) ? $snapshot['item_results'] : array();
        $entity_keys = array();
        $fallback_rows = array();
        foreach ($item_results as $item) {
            if (!is_array($item)) {
                continue;
            }
            $entity_key = sanitize_text_field((string) ($item['entity_key'] ?? ''));
            if ($entity_key === '') {
                continue;
            }
            $entity_keys[$entity_key] = true;
            if (!isset($fallback_rows[$entity_key])) {
                $fallback_rows[$entity_key] = array(
                    'entity_key' => $entity_key,
                    'title' => sanitize_text_field((string) ($item['title'] ?? $item['page_title'] ?? '')),
                    'url' => esc_url_raw((string) ($item['url'] ?? $item['page_url'] ?? $item['permalink'] ?? '')),
                );
            }
        }
        if ($project_id <= 0 || $entity_keys === array()) {
            return array(
                'rows' => array(),
                'pagination' => array('page' => 1, 'per_page' => 1, 'total' => 0, 'pages' => 1),
                'agent8_server_paginated' => true,
                'active_entity_group' => 'all',
                'agent8_entity_group_counts' => array(),
            );
        }

        $service = $this->aysa_onpage_workspace_service();
        $rows = array();
        foreach (array_keys($entity_keys) as $entity_key) {
            $row = $fallback_rows[$entity_key] ?? array('entity_key' => $entity_key);
            if ($service && method_exists($service, 'get_detail')) {
                // Execution item results already carry the persisted provider/public
                // verification statuses. Avoid one public HTTP readback per row here;
                // the report must remain responsive for the selected batch.
                $detail = $service->get_detail($project_id, $entity_key, false);
                $inventory = is_array($detail['inventory'] ?? null) ? $detail['inventory'] : array();
                if ($inventory !== array()) {
                    $row = array_merge($row, $inventory);
                }
            }
            $row['entity_key'] = $entity_key;
            $rows[] = $row;
        }

        return array(
            'rows' => $rows,
            'pagination' => array(
                'page' => 1,
                'per_page' => max(1, count($rows)),
                'total' => count($rows),
                'pages' => 1,
            ),
            'agent8_server_paginated' => true,
            'active_entity_group' => 'all',
            'agent8_entity_group_counts' => array('all' => count($rows)),
        );
    }

    private function aysa_agent8v3_onpage_execution_history_summary($project_id, $snapshot) {
        $snapshot = is_array($snapshot) ? $snapshot : array();
        $project_id = intval($project_id);
        if ($project_id <= 0 || !$this->aysa_agent8v3_is_onpage_execution_history_event($snapshot)) {
            return array();
        }

        $entity_keys = array();
        foreach (is_array($snapshot['item_results'] ?? null) ? $snapshot['item_results'] : array() as $item) {
            if (!is_array($item)) {
                continue;
            }
            $entity_key = sanitize_text_field((string) ($item['entity_key'] ?? ''));
            if ($entity_key !== '') {
                $entity_keys[$entity_key] = true;
            }
        }
        if ($entity_keys === array()) {
            return array();
        }

        $service = $this->aysa_onpage_workspace_service();
        if (!$service || !method_exists($service, 'get_detail')) {
            return array();
        }

        $summary = array();
        foreach (array_keys($entity_keys) as $entity_key) {
            // Execution reports already carry the provider/public verification snapshot.
            // Read local history and metadata only here; do not re-fetch each public URL.
            $detail = $service->get_detail($project_id, $entity_key, false);
            $history = is_array($detail['seo_history'] ?? null) ? $detail['seo_history'] : array();
            if ($history === array()) {
                continue;
            }
            $summary[$entity_key] = array(
                'history_id' => sanitize_text_field((string) ($history['id'] ?? '')),
                'status' => sanitize_key((string) ($history['status'] ?? '')),
                'is_applied' => !empty($history['is_applied']),
                'is_reverted' => !empty($history['is_reverted']),
                'previous_meta_title' => sanitize_text_field((string) ($history['previous_meta_title'] ?? '')),
                'previous_meta_description' => sanitize_textarea_field((string) ($history['previous_meta_description'] ?? '')),
                'applied_meta_title' => sanitize_text_field((string) ($history['meta_title'] ?? '')),
                'applied_meta_description' => sanitize_textarea_field((string) ($history['meta_description'] ?? '')),
                'applied_at' => sanitize_text_field((string) ($history['applied_at'] ?? $history['created_at'] ?? '')),
                'restore_available' => !empty($history['is_applied']) && empty($history['is_reverted']),
                'metadata_readback' => isset($detail['metadata_readback']) && is_array($detail['metadata_readback']) ? $detail['metadata_readback'] : array(),
            );
        }

        return $summary;
    }

    private function aysa_agent8v3_sync_latest_onpage_projection_snapshot($wp_project_id, $onpage_tab = '', $sync_mode = 'review', $budget_seconds = 20) {
        $wp_project_id = intval($wp_project_id);
        if ($wp_project_id <= 0) {
            return array('status' => 'skipped', 'reason' => 'missing_wp_project_id');
        }

        $budget_seconds = max(4, min(25, intval($budget_seconds)));
        $deadline = microtime(true) + $budget_seconds;

        $project_id = $this->aysa_agent8v3_project_id();
        $site_host = $this->aysa_agent8v3_site_host();
        $query = http_build_query(array(
            'project_id' => $project_id,
            'site_host' => $site_host,
        ), '', '&');
        $response = $this->aysa_agent8v3_api_request(
            'GET',
            '/v1/agent8v3/state' . ($query !== '' ? '?' . $query : ''),
            array(),
            max(1, min(5, $budget_seconds))
        );
        if (is_wp_error($response)) {
            return array(
                'status' => 'skipped',
                'reason' => sanitize_key($response->get_error_code()),
            );
        }
        if (empty($response['ok']) || !is_array($response)) {
            return array('status' => 'skipped', 'reason' => 'state_unavailable');
        }

        $state = is_array($response['state'] ?? null) ? $response['state'] : array();
        $onpage = is_array($state['data']['onpage_seo'] ?? null) ? $state['data']['onpage_seo'] : array();
        $state_version = intval($state['state_version'] ?? 0);
        $run_ids = is_array($onpage['run_ids'] ?? null) ? $onpage['run_ids'] : array();
        $onpage_status = sanitize_key((string) ($onpage['status'] ?? ''));
        $terminal_onpage_statuses = array('completed', 'completed_with_warnings', 'failed', 'cancelled', 'timeout');
        if ($state_version > 0 && $run_ids !== array() && !in_array($onpage_status, $terminal_onpage_statuses, true)) {
            // A modal read must not turn into a status refresh. The next explicit
            // run/status action owns that transition; return the current state now.
            return array(
                'status' => 'deferred',
                'reason' => 'onpage_run_not_terminal',
                'state_version' => $state_version,
                'run_ids' => $run_ids,
            );
        }
        $projections = is_array($onpage['projection'] ?? null) ? $onpage['projection'] : array();
        $service = $this->aysa_onpage_workspace_service();
        if (!$service || !method_exists($service, 'apply_projection')) {
            return array('status' => 'skipped', 'reason' => 'workspace_service_unavailable', 'state_version' => $state_version);
        }

        $coverage_snapshot = is_array($onpage['summary']['onpage_coverage'] ?? null)
            ? $onpage['summary']['onpage_coverage']
            : (is_array($onpage['coverage'] ?? null) ? $onpage['coverage'] : array());
        $sync_context = array(
            'state_version' => $state_version,
            'onpage_coverage' => $coverage_snapshot,
        );
        $projection_run_types = $this->aysa_agent8v3_onpage_projection_run_types_for_tab($onpage_tab, $sync_mode);
        if ($projection_run_types !== array()) {
            // The selected run endpoint hydrates durable task output. Aggregate Agent8 state may
            // legitimately lag behind the completed worker projection for the current review tab.
            if (microtime(true) + 1 >= $deadline) {
                return array_merge($sync_context, array(
                    'status' => 'deferred',
                    'reason' => 'modal_hydration_budget_exhausted',
                ));
            }
            if (sanitize_key((string) $sync_mode) === 'apply_readback') {
                $remaining_timeout = max(1, min(10, (int) floor($deadline - microtime(true))));
                $latest_apply_sync = $this->aysa_agent8v3_sync_latest_onpage_worker_projection($wp_project_id, $service, $sync_mode, $remaining_timeout);
                $latest_apply_snapshot = is_array($latest_apply_sync['local_projection_sync']['meta_apply'] ?? null)
                    ? $latest_apply_sync['local_projection_sync']['meta_apply']
                    : array();
                if ($latest_apply_snapshot !== array()) {
                    $attached_response = $this->aysa_agent8v3_attach_onpage_execution_snapshot_to_response(
                        $response,
                        array('wp_project_id' => $wp_project_id),
                        $latest_apply_snapshot,
                        array('meta_apply' => $latest_apply_sync['local_projection_sync'])
                    );
                    $latest_apply_sync['state_version'] = intval($attached_response['state']['state_version'] ?? $state_version);
                    $latest_apply_sync['billing_sync_status'] = !empty($attached_response['ok']) ? 'synced' : 'not_synced';
                    $latest_apply_sync['execution_snapshot'] = $latest_apply_snapshot;
                    return array_merge($sync_context, $latest_apply_sync);
                }
            }
            $selected_worker_sync = $this->aysa_agent8v3_sync_selected_onpage_worker_projections(
                $wp_project_id,
                $service,
                $run_ids,
                $projection_run_types,
                $sync_mode,
                $deadline
            );
            if (($selected_worker_sync['status'] ?? '') !== 'noop') {
                if (sanitize_key((string) $sync_mode) === 'apply_readback' && is_array($selected_worker_sync['meta_apply'] ?? null)) {
                    $attached_response = $this->aysa_agent8v3_attach_onpage_execution_snapshot_to_response(
                        $response,
                        array('wp_project_id' => $wp_project_id),
                        $selected_worker_sync['meta_apply'],
                        is_array($selected_worker_sync['local_projection_sync'] ?? null) ? $selected_worker_sync['local_projection_sync'] : array()
                    );
                    $sync_context['state_version'] = intval($attached_response['state']['state_version'] ?? $state_version);
                    $selected_worker_sync['billing_sync_status'] = !empty($attached_response['ok']) ? 'synced' : 'not_synced';
                    $selected_worker_sync['execution_snapshot'] = $selected_worker_sync['meta_apply'];
                }
                return array_merge($sync_context, $selected_worker_sync);
            }
            // The detailed run read can exhaust its bounded network window.
            // Continue with the same tab's aggregate projection already present
            // in the canonical Agent8 state; do not cascade to a generic latest
            // worker request.
        }

        if (empty($projections)) {
            if (microtime(true) + 1 >= $deadline) {
                return array_merge($sync_context, array(
                    'status' => 'deferred',
                    'reason' => 'modal_hydration_budget_exhausted',
                ));
            }
            $remaining_timeout = max(1, min(10, (int) floor($deadline - microtime(true))));
            $latest_worker_sync = $this->aysa_agent8v3_sync_latest_onpage_worker_projection($wp_project_id, $service, $sync_mode, $remaining_timeout);
            return array_merge($sync_context, $latest_worker_sync);
        }

        $run_ids = is_array($onpage['run_ids'] ?? null) ? $onpage['run_ids'] : array();
        $projection_run_types = $this->aysa_agent8v3_onpage_projection_run_types_for_tab($onpage_tab, $sync_mode);
        $sync_results = array();
        $onpage_execution_snapshot = array();
        foreach ($projections as $run_type => $projection) {
            if ($projection_run_types !== array() && !in_array(sanitize_key((string) $run_type), $projection_run_types, true)) {
                continue;
            }
            $has_inventory_rows = is_array($projection) && !empty($projection['inventory_rows']) && is_array($projection['inventory_rows']);
            $has_meta_apply_plan = is_array($projection) && !empty($projection['meta_apply_plan']) && is_array($projection['meta_apply_plan']);
            $has_mapping_updates = is_array($projection) && !empty($projection['mapping_updates']) && is_array($projection['mapping_updates']);
            if (!$has_inventory_rows && !$has_meta_apply_plan && !$has_mapping_updates) {
                continue;
            }
            $run_id = intval($run_ids[$run_type] ?? $projection['run_id'] ?? 0);
            $sync_results[$run_type] = $this->aysa_agent8v3_apply_onpage_projection_through_canonical_execution(
                $service,
                $wp_project_id,
                $run_id,
                $projection,
                array('wp_project_id' => $wp_project_id)
            );
            if ($run_type === 'meta_apply'
                && is_array($sync_results[$run_type]['meta_apply'] ?? null)
                && $sync_results[$run_type]['meta_apply'] !== array()
            ) {
                $onpage_execution_snapshot = $sync_results[$run_type]['meta_apply'];
            }
        }
        if (!empty($sync_results)) {
            $result = array(
                'status' => 'synced',
                'run_types' => array_values(array_keys($sync_results)),
                'state_version' => $state_version,
                'latest_worker_projection' => array(
                    'status' => 'skipped',
                    'reason' => 'aggregate_state_projections_are_canonical',
                ),
            );
            if (sanitize_key((string) $sync_mode) === 'apply_readback' && $onpage_execution_snapshot !== array()) {
                $attached_response = $this->aysa_agent8v3_attach_onpage_execution_snapshot_to_response(
                    $response,
                    array('wp_project_id' => $wp_project_id),
                    $onpage_execution_snapshot,
                    $sync_results
                );
                $result['state_version'] = intval($attached_response['state']['state_version'] ?? $state_version);
                $result['billing_sync_status'] = !empty($attached_response['ok']) ? 'synced' : 'not_synced';
                $result['execution_snapshot'] = $onpage_execution_snapshot;
            }
            return array_merge($sync_context, $result);
        }

        if (microtime(true) + 10 > $deadline) {
            return array_merge($sync_context, array(
                'status' => 'deferred',
                'reason' => 'modal_hydration_budget_exhausted',
            ));
        }
        $remaining_timeout = max(1, min(10, (int) floor($deadline - microtime(true))));
        $latest_worker_sync = $this->aysa_agent8v3_sync_latest_onpage_worker_projection($wp_project_id, $service, $sync_mode, $remaining_timeout);
        return array_merge($sync_context, $latest_worker_sync);
    }

    private function aysa_agent8v3_onpage_projection_run_types_for_tab($onpage_tab, $sync_mode = 'review') {
        $onpage_tab = sanitize_key((string) $onpage_tab);
        $by_tab = array(
            'keyword_mapping' => array('mapping_sync', 'mapping_autofill', 'keyword_mapping_ai', 'keyword_validate_for_page'),
            'meta_snippets' => array('meta_generate'),
            'content' => array('rewrite_decide', 'rewrite_generate'),
            'aeo_content_qa' => array('output_reconcile'),
            'links_authority' => array('output_reconcile'),
            'images' => array('output_reconcile'),
            'execution_report' => array('output_reconcile'),
        );
        if ($onpage_tab === 'meta_snippets' && sanitize_key((string) $sync_mode) === 'apply_readback') {
            return array('meta_apply');
        }
        if ($onpage_tab === 'execution_report' && sanitize_key((string) $sync_mode) === 'apply_readback') {
            return array('meta_apply');
        }
        $run_types = isset($by_tab[$onpage_tab]) ? $by_tab[$onpage_tab] : array();
        return array_values(array_unique($run_types));
    }

    private function aysa_agent8v3_sync_latest_onpage_worker_projection($wp_project_id, $service, $sync_mode = 'review', $timeout_seconds = 10) {
        $result = $this->aysa_broker_request('POST', '/v1/onpage-runs/status', array(
            'project_id' => intval($wp_project_id),
            'run_id' => 0,
            'after_event_id' => 0,
        ), max(1, min(10, intval($timeout_seconds))));
        if (is_wp_error($result)) {
            return array('status' => 'skipped', 'reason' => sanitize_key($result->get_error_code()));
        }
        if (empty($result['projection']) || empty($result['run']['id'])) {
            return array('status' => 'noop');
        }
        $projection = (array) $result['projection'];
        if (sanitize_key((string) $sync_mode) === 'apply_readback') {
            $projection['refresh_inventory'] = false;
            unset($projection['inventory_rows']);
        }
        $sync = $this->aysa_agent8v3_apply_onpage_projection_through_canonical_execution(
            $service,
            intval($wp_project_id),
            intval($result['run']['id']),
            $projection,
            array('wp_project_id' => intval($wp_project_id))
        );
        return array(
            'status' => 'synced_latest',
            'run_id' => intval($result['run']['id']),
            'local_projection_sync' => $sync,
        );
    }

    private function aysa_agent8v3_sync_selected_onpage_worker_projections($wp_project_id, $service, $run_ids, $run_types, $sync_mode = 'review', $deadline = 0) {
        $run_ids = is_array($run_ids) ? $run_ids : array();
        $run_types = is_array($run_types) ? $run_types : array();
        if ($run_ids === array() || $run_types === array()) {
            return array('status' => 'noop');
        }

        $sync_results = array();
        foreach ($run_types as $run_type) {
            $run_type = sanitize_key((string) $run_type);
            $run_id = intval($run_ids[$run_type] ?? 0);
            if ($run_type === '' || $run_id <= 0) {
                continue;
            }
            $remaining_timeout = $deadline > 0 ? (int) floor($deadline - microtime(true)) : 10;
            if ($remaining_timeout < 1) {
                break;
            }
            $result = $this->aysa_broker_request('POST', '/v1/onpage-runs/status', array(
                'project_id' => intval($wp_project_id),
                'run_id' => $run_id,
                'after_event_id' => 0,
            ), max(1, min(5, $remaining_timeout)));
            if (is_wp_error($result) || empty($result['projection']) || intval($result['run']['id'] ?? 0) !== $run_id) {
                continue;
            }
            $projection = (array) $result['projection'];
            if (sanitize_key((string) $sync_mode) === 'apply_readback') {
                $projection['refresh_inventory'] = false;
                unset($projection['inventory_rows']);
            }
            $sync_results[$run_type] = $this->aysa_agent8v3_apply_onpage_projection_through_canonical_execution(
                $service,
                intval($wp_project_id),
                $run_id,
                $projection,
                array('wp_project_id' => intval($wp_project_id))
            );
        }

        if ($sync_results === array()) {
            return array('status' => 'noop');
        }
        $meta_apply = array();
        foreach ($sync_results as $sync_result) {
            if (is_array($sync_result['meta_apply'] ?? null) && $sync_result['meta_apply'] !== array()) {
                $meta_apply = $sync_result['meta_apply'];
                break;
            }
        }
        return array(
            'status' => 'synced_selected',
            'run_types' => array_values(array_keys($sync_results)),
            'run_ids' => array_values(array_filter(array_map(static function($result) {
                return intval($result['run_id'] ?? 0);
            }, $sync_results))),
            'run_ids_by_type' => array_map(static function($result) {
                return intval($result['run_id'] ?? 0);
            }, $sync_results),
            'local_projection_sync' => $sync_results,
            'meta_apply' => $meta_apply,
        );
    }

    /**
     * Keep worker projection hydration and WordPress persistence on the existing
     * durable Agent8 executor. The workspace service still owns inventory and
     * mapping projection, but it must never become a second Metadata writer.
     */
    private function aysa_agent8v3_apply_onpage_projection_through_canonical_execution($service, $project_id, $run_id, $projection, $request_payload = array()) {
        $projection = is_array($projection) ? $projection : array();
        $request_payload = is_array($request_payload) ? $request_payload : array();
        $meta_apply_result = array();
        $meta_apply_plan = is_array($projection['meta_apply_plan'] ?? null)
            ? $projection['meta_apply_plan']
            : array();

        if ($meta_apply_plan !== array()) {
            $items = is_array($meta_apply_plan['items'] ?? null)
                ? array_values(array_filter($meta_apply_plan['items'], 'is_array'))
                : array();
            if (intval($meta_apply_plan['source_metadata_run_id'] ?? 0) <= 0) {
                $provider_run_ids = array();
                foreach ($items as $item) {
                    $provider_output_id = sanitize_text_field((string) ($item['provider_output_id'] ?? ''));
                    if (preg_match('/^(\d+):/', $provider_output_id, $provider_run_match)) {
                        $provider_run_ids[intval($provider_run_match[1])] = true;
                    }
                }
                if (count($provider_run_ids) === 1) {
                    $meta_apply_plan['source_metadata_run_id'] = intval(array_key_first($provider_run_ids));
                }
            }
            if (intval($meta_apply_plan['source_metadata_run_id'] ?? 0) <= 0
                && $items !== array()
                && is_object($service)
                && method_exists($service, 'build_run_selection_meta')) {
                $selected_entity_keys = array_values(array_unique(array_filter(array_map(static function($item) {
                    return sanitize_text_field((string) ($item['entity_key'] ?? $item['item_id'] ?? ''));
                }, $items))));
                $selected_contexts = $selected_entity_keys !== array()
                    ? $service->build_run_selection_meta(intval($project_id), 'all', array(), $selected_entity_keys)
                    : array();
                $context_by_entity = array();
                $workspace_run_ids = array();
                foreach ($selected_contexts as $selected_context) {
                    if (!is_array($selected_context)) {
                        continue;
                    }
                    $context_entity_key = sanitize_text_field((string) ($selected_context['entity_key'] ?? ''));
                    $context_worker_run_id = intval($selected_context['worker_run_id'] ?? 0);
                    if ($context_entity_key === '' || !in_array($context_entity_key, $selected_entity_keys, true)) {
                        continue;
                    }
                    $context_by_entity[$context_entity_key] = true;
                    if ($context_worker_run_id > 0) {
                        $workspace_run_ids[$context_worker_run_id] = true;
                    }
                }
                if (count($context_by_entity) === count($selected_entity_keys) && count($workspace_run_ids) === 1) {
                    $meta_apply_plan['source_metadata_run_id'] = intval(array_key_first($workspace_run_ids));
                }
            }
            if (intval($meta_apply_plan['max_total_cost_cents'] ?? 0) <= 0 && $items !== array()) {
                $meta_apply_plan['max_total_cost_cents'] = count($items) * 2 * 5;
            }
            $replay_key = sanitize_text_field((string) ($meta_apply_plan['idempotency_key'] ?? ''));
            $replay_hash = sanitize_text_field((string) ($meta_apply_plan['payload_hash'] ?? ''));
            $persisted_replays = get_option('aysa_agent8v3_onpage_execution_idempotency', array());
            $persisted_replays = is_array($persisted_replays) ? $persisted_replays : array();
            $persisted_replay = is_array($persisted_replays[$replay_key] ?? null)
                ? $persisted_replays[$replay_key]
                : array();
            $durable_replay_matches = $replay_key !== ''
                && $replay_hash !== ''
                && hash_equals($replay_hash, sanitize_text_field((string) ($persisted_replay['payload_hash'] ?? '')))
                && (string) ($persisted_replay['durable_identity_status'] ?? '') === 'complete'
                && !empty($persisted_replay['pre_execution_snapshot_persisted'])
                && !empty($persisted_replay['post_execution_snapshot_persisted'])
                && !empty($persisted_replay['terminal_snapshot_persisted']);
            if ($durable_replay_matches) {
                $meta_apply_plan['confirmation_kind'] = sanitize_key((string) ($persisted_replay['confirmation_kind'] ?? ''));
                $meta_apply_plan['onpage_seo_apply_accepted_bundle'] = true;
                $meta_apply_plan['user_confirmed'] = !empty($persisted_replay['user_confirmed']);
                $meta_apply_plan['confirmed_by_user'] = !empty($persisted_replay['user_confirmed']);
                $meta_apply_plan['source_run_id'] = intval($persisted_replay['source_run_id'] ?? $run_id);
                $meta_apply_plan['source_metadata_run_id'] = intval($persisted_replay['source_metadata_run_id'] ?? 0);
                $meta_apply_plan['max_total_cost_cents'] = intval($persisted_replay['max_total_cost_cents'] ?? 0);
            }
            if (!isset($meta_apply_plan['payload']) || !is_array($meta_apply_plan['payload'])) {
                $meta_apply_plan['payload'] = array();
            }
            $meta_apply_plan['payload']['accepted_items'] = $items;
            $meta_apply_plan['run_id'] = intval($meta_apply_plan['run_id'] ?? $run_id);
            $meta_apply_plan['source_run_id'] = intval($meta_apply_plan['source_run_id'] ?? 0) > 0
                ? intval($meta_apply_plan['source_run_id'])
                : intval($run_id);
            $request_payload['wp_project_id'] = intval($request_payload['wp_project_id'] ?? $project_id);
            $meta_apply_result = $this->aysa_agent8v3_apply_onpage_execution_plan($meta_apply_plan, $request_payload);
            unset($projection['meta_apply_plan']);
        }

        $sync = $service->apply_projection(intval($project_id), intval($run_id), $projection);
        $sync = is_array($sync) ? $sync : array();
        if ($meta_apply_result !== array()) {
            $sync['meta_apply'] = $meta_apply_result;
        }
        return $sync;
    }

    private function aysa_agent8v3_extract_activation_key_from_payload($request_payload) {
        $request_payload = is_array($request_payload) ? $request_payload : array();
        $candidates = array();
        if (isset($request_payload['message'])) {
            $candidates[] = (string) $request_payload['message'];
        }
        if (isset($request_payload['payload']) && is_array($request_payload['payload'])) {
            foreach (array('activation_key', 'access_key', 'value') as $key) {
                if (isset($request_payload['payload'][$key])) {
                    $candidates[] = (string) $request_payload['payload'][$key];
                }
            }
        }
        foreach (array('activation_key', 'access_key', 'value') as $key) {
            if (isset($request_payload[$key])) {
                $candidates[] = (string) $request_payload[$key];
            }
        }
        foreach ($candidates as $candidate) {
            $candidate = trim($candidate);
            if ($candidate === '') {
                continue;
            }
            if (preg_match('/(AYSA-[A-Z0-9-]{8,}|aysa_(?:pk|sk)_[a-z0-9]{16,}|[A-Za-z0-9\\-]{32,})/i', $candidate, $matches)) {
                return sanitize_text_field($matches[1]);
            }
            if (strlen($candidate) >= 12 && strlen($candidate) <= 256 && strpos($candidate, ' ') === false) {
                return sanitize_text_field($candidate);
            }
        }
        return '';
    }

    private function aysa_agent8v3_log_legacy_commit($plan, $result) {
        $events = get_option('aysa_agent8v3_legacy_commits', array());
        $events = is_array($events) ? $events : array();
        array_unshift($events, array(
            'action' => sanitize_key((string) ($result['action'] ?? $plan['action'] ?? '')),
            'state_version' => intval($plan['state_version'] ?? 0),
            'payload_hash' => sanitize_text_field((string) ($result['payload_hash'] ?? $plan['payload_hash'] ?? '')),
            'option_keys' => is_array($result['option_keys'] ?? null) ? array_values(array_map('sanitize_text_field', $result['option_keys'])) : array(),
            'status' => sanitize_key((string) ($result['status'] ?? 'unknown')),
            'error' => isset($result['error']) ? sanitize_text_field((string) $result['error']) : null,
            'timestamp' => current_time('mysql'),
        ));
        update_option('aysa_agent8v3_legacy_commits', array_slice($events, 0, 50), false);
    }

    private function aysa_agent8v3_apply_content_planner_commit_plan($plan, $request_payload) {
        global $wpdb;
        $plan = is_array($plan) ? $plan : array();
        $idempotency_key = sanitize_text_field((string) ($plan['idempotency_key'] ?? ''));
        $payload_hash = sanitize_text_field((string) ($plan['payload_hash'] ?? ''));
        $rows = is_array($plan['payload']['rows'] ?? null) ? $plan['payload']['rows'] : array();
        $result = array(
            'action' => 'approve_content_plan',
            'payload_hash' => $payload_hash,
            'accepted_count' => count($rows),
            'inserted_count' => 0,
            'skipped_count' => 0,
            'verified_count' => 0,
            'verified_items' => array(),
            'status' => 'skipped',
            'idempotency_key' => $idempotency_key,
            'option_keys' => array('wp_aysa_content_strategies', 'aysa_agent8v3_content_planner_commit_idempotency'),
        );
        if ($idempotency_key === '' || empty($rows)) {
            $result['status'] = 'rejected';
            $result['error'] = 'content_planner_commit_plan_invalid';
            $this->aysa_agent8v3_log_content_planner_commit($plan, $result);
            return $result;
        }

        $applied = get_option('aysa_agent8v3_content_planner_commit_idempotency', array());
        $applied = is_array($applied) ? $applied : array();
        if (!empty($applied[$idempotency_key])) {
            $result = array_merge($result, is_array($applied[$idempotency_key]) ? $applied[$idempotency_key] : array());
            $result['status'] = 'already_synced';
            $this->aysa_agent8v3_log_content_planner_commit($plan, $result);
            return $result;
        }

        $table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table)) !== $table) {
            if (class_exists('Aysa_Content_Strategy_Manager')) {
                $manager = new Aysa_Content_Strategy_Manager();
                if (method_exists($manager, 'create_table')) {
                    $manager->create_table();
                }
            }
        }
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table)) !== $table) {
            $result['status'] = 'failed';
            $result['error'] = 'content_strategy_table_missing';
            $this->aysa_agent8v3_log_content_planner_commit($plan, $result);
            return $result;
        }

        $project_id = intval($request_payload['wp_project_id'] ?? get_option('aysa_project_id', 0));
        $plan_batch = 'agent8v3-' . substr($payload_hash, 0, 12);
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $title = sanitize_text_field((string) ($row['title'] ?? ''));
            if ($title === '') {
                continue;
            }
            $primary_keyword = sanitize_text_field((string) ($row['primary_keyword'] ?? ''));
            $target_url = esc_url_raw((string) ($row['target_url'] ?? ''));
            $content_fingerprint = hash('sha256', strtolower($title . '|' . $primary_keyword . '|' . $target_url));
            $intervention_callback = class_exists('Aysa_AI_Visibility_Intervention_Correlation')
                ? Aysa_AI_Visibility_Intervention_Correlation::normalize($row['ai_visibility_intervention_callback'] ?? array())
                : array();
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $existing = $wpdb->get_var($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT id FROM {$table} WHERE project_id = %d AND content_fingerprint = %s LIMIT 1",
                $project_id,
                $content_fingerprint
            ));
            if (!empty($existing)) {
                $result['skipped_count']++;
                if (!empty($intervention_callback)) {
                    $result['verified_items'][] = array(
                        'content_strategy_id' => intval($existing),
                        'status' => 'already_present',
                        'verified' => true,
                        'target_url' => $target_url,
                        'ai_visibility_intervention_callback' => $intervention_callback,
                        'verified_at' => current_time('mysql'),
                    );
                }
                continue;
            }
            $keywords = array();
            if (is_array($row['keywords'] ?? null)) {
                foreach ($row['keywords'] as $keyword) {
                    $keyword = sanitize_text_field((string) $keyword);
                    if ($keyword !== '') {
                        $keywords[] = $keyword;
                    }
                }
            }
            if ($primary_keyword !== '') {
                array_unshift($keywords, $primary_keyword);
            }
            $keywords = array_values(array_unique($keywords));
            $scheduled_for = $this->aysa_agent8v3_mysql_datetime((string) ($row['scheduled_for'] ?? ''));
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $inserted = $wpdb->insert($table, array(
                'project_id' => $project_id,
                'title' => $title,
                'keywords' => wp_json_encode($keywords),
                'intent' => '',
                'associated_page' => $target_url,
                'strategy_type' => 'agent8v3_research',
                'strategy_meta' => wp_json_encode(array_filter(array(
                    'agent8v3' => true,
                    'run_id' => intval($plan['run_id'] ?? 0),
                    'content_item_id' => sanitize_text_field((string) ($row['content_item_id'] ?? '')),
                    'source' => sanitize_text_field((string) ($row['source'] ?? '')),
                    'ai_visibility_intervention_callback' => !empty($intervention_callback) ? $intervention_callback : null,
                ), static function ($value) {
                    return $value !== null && $value !== '';
                })),
                'canonical_topic' => $primary_keyword,
                'content_fingerprint' => $content_fingerprint,
                'recommended_page_type' => sanitize_key((string) ($row['content_type'] ?? 'article')),
                'content_type' => sanitize_key((string) ($row['content_type'] ?? 'article')),
                'execution_stage' => 'planned',
                'status' => 'planned',
                'scheduled_for' => $scheduled_for,
                'automation_status' => 'approval_required',
                'automation_attempts' => 0,
                'plan_batch' => $plan_batch,
                'automation_updated_at' => current_time('mysql'),
                'created_at' => current_time('mysql'),
            ));
            if ($inserted) {
                $result['inserted_count']++;
                if (!empty($intervention_callback)) {
                    $result['verified_count']++;
                    $result['verified_items'][] = array(
                        'content_strategy_id' => intval($wpdb->insert_id),
                        'status' => 'inserted',
                        'verified' => true,
                        'target_url' => $target_url,
                        'ai_visibility_intervention_callback' => $intervention_callback,
                        'verified_at' => current_time('mysql'),
                    );
                }
            }
        }

        $result['status'] = 'synced';
        $applied[$idempotency_key] = array(
            'payload_hash' => $payload_hash,
            'accepted_count' => count($rows),
            'inserted_count' => intval($result['inserted_count']),
            'skipped_count' => intval($result['skipped_count']),
            'verified_count' => intval($result['verified_count']),
            'verified_items' => $result['verified_items'],
            'idempotency_key' => $idempotency_key,
            'option_keys' => $result['option_keys'],
            'timestamp' => current_time('mysql'),
        );
        $result['timestamp'] = current_time('mysql');
        update_option('aysa_agent8v3_content_planner_commit_idempotency', array_slice($applied, -100, null, true), false);
        $this->aysa_agent8v3_log_content_planner_commit($plan, $result);
        return $result;
    }

    private function aysa_agent8v3_mysql_datetime($value) {
        $timestamp = strtotime((string) $value);
        if (!$timestamp) {
            $timestamp = time() + 300;
        }
        return gmdate('Y-m-d H:i:s', $timestamp);
    }

    private function aysa_agent8v3_log_content_planner_commit($plan, $result) {
        $events = get_option('aysa_agent8v3_content_planner_commits', array());
        $events = is_array($events) ? $events : array();
        array_unshift($events, array(
            'action' => 'approve_content_plan',
            'state_version' => intval($plan['state_version'] ?? 0),
            'accepted_count' => intval($result['accepted_count'] ?? 0),
            'rejected_count' => intval($plan['rejected_count'] ?? 0),
            'payload_hash' => sanitize_text_field((string) ($result['payload_hash'] ?? $plan['payload_hash'] ?? '')),
            'status' => sanitize_key((string) ($result['status'] ?? 'unknown')),
            'error' => isset($result['error']) ? sanitize_text_field((string) $result['error']) : null,
            'timestamp' => current_time('mysql'),
        ));
        update_option('aysa_agent8v3_content_planner_commits', array_slice($events, 0, 50), false);
    }

    private function aysa_agent8v3_onpage_content_rewrite_apply_dry_run_bridge($payload) {
        $payload = is_array($payload) ? $payload : array();
        $selection = isset($payload['selection']) && is_array($payload['selection'])
            ? array_values(array_filter(array_map('sanitize_text_field', $payload['selection'])))
            : array();
        $accepted_items = isset($payload['accepted_items']) && is_array($payload['accepted_items'])
            ? array_values(array_filter($payload['accepted_items'], 'is_array'))
            : (isset($payload['payload']['accepted_items']) && is_array($payload['payload']['accepted_items'])
                ? array_values(array_filter($payload['payload']['accepted_items'], 'is_array'))
                : array());
        $item_results = array();
        $validated_count = 0;
        $manual_review_count = 0;
        foreach ($accepted_items as $item) {
            $item = is_array($item) ? $item : array();
            $entity_key = sanitize_text_field((string) ($item['entity_key'] ?? $item['item_id'] ?? ''));
            $before_hash = sanitize_text_field((string) ($item['before_hash'] ?? $item['current_hash'] ?? $item['content_before_hash'] ?? ''));
            $after_hash = sanitize_text_field((string) ($item['after_hash'] ?? $item['proposed_hash'] ?? $item['content_after_hash'] ?? ''));
            $surface = sanitize_key((string) ($item['content_surface'] ?? $item['surface'] ?? 'unknown'));
            $target = sanitize_key((string) ($item['target'] ?? $item['field'] ?? 'post_content'));
            $supported_surface = !in_array($surface, array('elementor', 'wpbakery', 'divi', 'unknown'), true);
            $blocking_reasons = array();
            if ($entity_key === '') {
                $blocking_reasons[] = 'entity_key_required';
            }
            if ($before_hash === '' || $after_hash === '') {
                $blocking_reasons[] = 'before_after_hash_required';
            }
            if ($before_hash !== '' && $after_hash !== '' && hash_equals($before_hash, $after_hash)) {
                $blocking_reasons[] = 'no_content_change';
            }
            if (!$supported_surface) {
                $blocking_reasons[] = 'unsupported_content_surface';
            }
            $status = $blocking_reasons === array() ? 'dry_run_validated' : 'manual_review_needed';
            if ($status === 'dry_run_validated') {
                $validated_count++;
            } else {
                $manual_review_count++;
            }
            $item_results[] = array(
                'item_id' => sanitize_text_field((string) ($item['item_id'] ?? $entity_key)),
                'entity_key' => $entity_key,
                'target' => $target,
                'content_surface' => $surface,
                'before_hash' => $before_hash,
                'after_hash' => $after_hash,
                'status' => $status,
                'dry_run' => true,
                'writes_enabled' => false,
                'billing_enabled' => false,
                'blocking_reasons' => $blocking_reasons,
            );
        }
        $payload_hash = sanitize_text_field((string) ($payload['payload_hash'] ?? ''));
        if ($payload_hash === '') {
            $encoded_items = wp_json_encode($accepted_items);
            $payload_hash = hash('sha256', is_string($encoded_items) ? $encoded_items : maybe_serialize($accepted_items));
        }
        $idempotency_key = sanitize_text_field((string) ($payload['idempotency_key'] ?? ''));
        if ($idempotency_key === '') {
            $idempotency_key = 'agent8v3:onpage_content_rewrite_dry_run:' . substr($payload_hash, 0, 32);
        }
        $status = $validated_count > 0
            ? ($manual_review_count > 0 ? 'dry_run_validated_with_warnings' : 'dry_run_validated')
            : 'dry_run_blocked';
        $controlled_readiness = $this->aysa_agent8v3_onpage_content_rewrite_controlled_apply_readiness(count($accepted_items));

        return array(
            'code' => 'onpage_content_rewrite_apply_dry_run_bridge',
            'message' => 'Content Rewrite a fost verificat în dry-run. Nu se scrie conținut și nu se debitează credite.',
            'write_class' => 'onpage_content_rewrite_apply',
            'operation' => 'onpage_content_rewrite_apply_bridge',
            'status' => $status,
            'dry_run' => true,
            'writes_enabled' => false,
            'billing_enabled' => false,
            'billing_status' => 'not_billable_dry_run',
            'billing_cost_cents' => 0,
            'actual_credits_cents' => 0,
            'worker_task_group' => 'onpage_apply_content_rewrite_dry_run',
            'plugin_bridge' => 'onpage_content_rewrite_apply_dry_run_bridge',
            'apply_gate_status' => 'off',
            'controlled_apply_status' => 'readiness_contract_ready_write_disabled',
            'controlled_apply_ready' => false,
            'controlled_apply_readiness' => $controlled_readiness,
            'write_execution_status' => 'blocked_until_controlled_gate',
            'live_write_requires_separate_confirmation' => true,
            'selected_count' => count($selection),
            'accepted_count' => count($accepted_items),
            'validated_count' => $validated_count,
            'manual_review_count' => $manual_review_count,
            'idempotency_key' => $idempotency_key,
            'payload_hash' => $payload_hash,
            'item_results' => $item_results,
            'required_before_enable' => array(
                'api_gate_on',
                'plugin_site_gate_on',
                'explicit_selected_rows',
                'worker_task_write_execution',
                'plugin_bridge_write_allowlist',
                'before_after_content_history',
                'readback_verification',
                'hash_conflict_safe_restore',
                'verified_only_billing',
                'controlled_live_test',
            ),
        );
    }

    private function aysa_agent8v3_onpage_content_rewrite_controlled_apply_readiness($selected_count) {
        $selected_count = max(0, intval($selected_count));
        return array(
            'status' => 'gate_off_no_write',
            'ready' => false,
            'dry_run_only' => true,
            'writes_enabled' => false,
            'billing_enabled' => false,
            'selected_rows' => $selected_count,
            'allowed_write_targets' => array(
                'post_content',
                'post_excerpt',
                'term_description',
            ),
            'worker_task_group' => 'onpage_apply_content_rewrite_controlled_gate_off',
            'plugin_bridge' => 'onpage_content_rewrite_apply_controlled_bridge',
            'billing_model' => 'verified_changed_content_targets_only_cost_x5_gate_off',
            'executor_available' => true,
            'requires_separate_live_write_confirmation' => true,
            'gates' => array(
                'api_gate_on' => false,
                'plugin_site_gate_on' => false,
                'explicit_selected_rows' => $selected_count > 0,
                'supported_content_surface' => 'required',
                'target_allowlist' => array(
                    'post_content',
                    'post_excerpt',
                    'term_description',
                ),
                'before_hash_match' => 'required',
                'after_hash_present' => 'required',
                'history_snapshot' => 'required',
                'restore_available' => 'required',
                'readback_verification' => 'required',
                'verified_only_billing' => 'required',
                'controlled_live_write_approval' => 'required',
            ),
        );
    }

    private function aysa_agent8v3_onpage_content_rewrite_product_gates() {
        $project_gate = get_option('aysa_agent8v3_onpage_content_rewrite_project_gate_enabled', '0') === '1';
        $site_gate = get_option('aysa_agent8v3_onpage_content_rewrite_site_gate_enabled', '0') === '1'
            || get_option('aysa_agent8v3_onpage_content_rewrite_apply_enabled', '0') === '1';
        $kill_switch = $this->aysa_agent8v3_env_flag('AYSA_AGENT8V3_ONPAGE_APPLY_KILL_SWITCH')
            || $this->aysa_agent8v3_env_flag('AYSA_AGENT8V3_ONPAGE_CONTENT_REWRITE_APPLY_KILL_SWITCH');
        $scope = get_option('aysa_agent8v3_onpage_content_rewrite_scoped_gate', array());
        $scope = is_array($scope) ? $scope : array();
        $expires_at = intval($scope['expires_at'] ?? 0);
        if ($expires_at > 0 && $expires_at < time()) {
            $project_gate = false;
            $site_gate = false;
        }

        return array(
            'module' => 'onpage_content_rewrite',
            'project_gate_enabled' => $project_gate,
            'api_project_gate_enabled' => $project_gate,
            'site_gate_enabled' => $site_gate,
            'plugin_site_gate_enabled' => $site_gate,
            'global_kill_switch_active' => $kill_switch,
            'normal_flow_enabled' => $project_gate && $site_gate && !$kill_switch,
            'default_state' => 'off',
            'source' => 'plugin_admin_options',
            'scope_required' => 'project_site_entity_target_idempotency',
            'scoped_gate' => array(
                'enabled' => !empty($scope['enabled']) && $project_gate && $site_gate && !$kill_switch,
                'entity_key' => sanitize_text_field((string) ($scope['entity_key'] ?? '')),
                'target' => sanitize_key((string) ($scope['target'] ?? '')),
                'payload_hash' => sanitize_text_field((string) ($scope['payload_hash'] ?? '')),
                'idempotency_key' => sanitize_text_field((string) ($scope['idempotency_key'] ?? '')),
                'expires_at' => $expires_at,
            ),
        );
    }

    public function handle_agent8v3_onpage_content_rewrite_product_gates_set() {
        if (!$this->aysa_agent8v3_image_alt_test_authorized()) {
            wp_send_json_error(array('message' => 'Unauthorized'), 403);
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_gate = !empty($_POST['project_gate_enabled']) && sanitize_text_field((string) wp_unslash($_POST['project_gate_enabled'])) !== '0';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $site_gate = !empty($_POST['site_gate_enabled']) && sanitize_text_field((string) wp_unslash($_POST['site_gate_enabled'])) !== '0';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $entity_key = sanitize_text_field((string) wp_unslash($_POST['entity_key'] ?? ''));
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $target = sanitize_key((string) wp_unslash($_POST['target'] ?? ''));
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $payload_hash = sanitize_text_field((string) wp_unslash($_POST['payload_hash'] ?? ''));
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $idempotency_key = sanitize_text_field((string) wp_unslash($_POST['idempotency_key'] ?? ''));
        $enable_scope = $project_gate && $site_gate && $entity_key !== '' && $target !== '' && $payload_hash !== '' && $idempotency_key !== '';

        update_option('aysa_agent8v3_onpage_content_rewrite_project_gate_enabled', $project_gate ? '1' : '0', false);
        update_option('aysa_agent8v3_onpage_content_rewrite_site_gate_enabled', $site_gate ? '1' : '0', false);
        update_option('aysa_agent8v3_onpage_content_rewrite_apply_enabled', $site_gate ? '1' : '0', false);
        update_option('aysa_agent8v3_onpage_content_rewrite_scoped_gate', array(
            'enabled' => $enable_scope,
            'entity_key' => $entity_key,
            'target' => $target,
            'payload_hash' => $payload_hash,
            'idempotency_key' => $idempotency_key,
            'enabled_by' => get_current_user_id(),
            'enabled_at' => time(),
            'expires_at' => time() + 15 * MINUTE_IN_SECONDS,
        ), false);

        wp_send_json_success(array(
            'product_gates' => $this->aysa_agent8v3_onpage_content_rewrite_product_gates(),
            'billing_status' => 'content_rewrite_real_billing',
            'billing_action_key' => 'agent8v3_onpage_content_rewrite_apply',
            'billing_unit_cents' => 5,
            'scope_enabled' => $enable_scope,
        ));
    }

    public function handle_agent8v3_onpage_content_rewrite_live_refresh() {
        if (!$this->aysa_agent8v3_image_alt_test_authorized()) {
            wp_send_json_error(array('message' => 'Unauthorized'), 403);
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $item_json = (string) wp_unslash($_POST['item_json'] ?? '');
        $item = $item_json !== '' ? json_decode($item_json, true) : array();
        if (!is_array($item)) {
            wp_send_json_error(array('message' => 'Invalid Content Rewrite item.'), 400);
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        $target = sanitize_key((string) ($item['target'] ?? $item['field'] ?? ''));
        if ($project_id <= 0 || $target === '') {
            wp_send_json_error(array('message' => 'Content Rewrite refresh is missing project or target.'), 400);
        }

        $values = $this->aysa_agent8v3_onpage_content_rewrite_resolve_target_values($project_id, $item, $target);
        if ((string) ($values['status'] ?? '') !== 'read_current_target') {
            wp_send_json_error(array(
                'message' => 'Content Rewrite live target could not be read.',
                'status' => sanitize_key((string) ($values['status'] ?? 'target_not_read')),
            ), 409);
        }

        $refreshed = array_merge($item, array(
            'entity_key' => sanitize_text_field((string) ($values['entity_key'] ?? ($item['entity_key'] ?? $item['item_id'] ?? ''))),
            'entity_id' => intval($values['entity_id'] ?? ($item['entity_id'] ?? 0)),
            'object_kind' => sanitize_key((string) ($values['object_kind'] ?? ($item['object_kind'] ?? 'post'))),
            'taxonomy' => sanitize_key((string) ($values['taxonomy'] ?? ($item['taxonomy'] ?? ''))),
            'target' => sanitize_key((string) ($values['target'] ?? $target)),
            'content_key' => sanitize_key((string) ($values['content_key'] ?? ($item['content_key'] ?? ''))),
            'current_value' => (string) ($values['current_value'] ?? ''),
            'before_hash' => sanitize_text_field((string) ($values['current_hash'] ?? '')),
            'proposed_value' => (string) ($values['proposed_value'] ?? ($item['proposed_value'] ?? '')),
            'after_hash' => sanitize_text_field((string) ($values['proposed_hash'] ?? ($item['after_hash'] ?? ''))),
            'op27_live_refresh' => true,
            'op27_refreshed_at' => current_time('mysql'),
        ));

        $accepted_items = array($refreshed);
        $encoded_items = wp_json_encode($accepted_items);
        $payload_hash = hash('sha256', is_string($encoded_items) ? $encoded_items : maybe_serialize($accepted_items));
        $idempotency_key = 'agent8v3:onpage_content_rewrite_controlled_apply:' . substr($payload_hash, 0, 32);
        $current_preview = trim(preg_replace('/\s+/u', ' ', wp_strip_all_tags((string) ($values['current_value'] ?? ''))) ?: '');

        wp_send_json_success(array(
            'read_only' => true,
            'refreshed_item' => $refreshed,
            'payload_hash' => $payload_hash,
            'idempotency_key' => $idempotency_key,
            'current_hash' => sanitize_text_field((string) ($values['current_hash'] ?? '')),
            'proposed_hash' => sanitize_text_field((string) ($values['proposed_hash'] ?? '')),
            'before_hash_changed' => sanitize_text_field((string) ($item['before_hash'] ?? '')) !== sanitize_text_field((string) ($values['current_hash'] ?? '')),
            'current_preview' => mb_substr($current_preview, 0, 280),
        ));
    }

    public function handle_agent8v3_onpage_content_rewrite_history_action() {
        if (!$this->aysa_agent8v3_image_alt_test_authorized()) {
            wp_send_json_error(array('message' => 'Unauthorized'), 403);
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $operation = sanitize_key((string) wp_unslash($_POST['operation'] ?? 'list'));
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $history_id = sanitize_text_field((string) wp_unslash($_POST['history_id'] ?? ''));
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = intval($_POST['project_id'] ?? get_option('aysa_project_id', 0));
        if ($project_id <= 0) {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
        }

        if ($operation === 'list') {
            wp_send_json_success(array(
                'operation' => 'list',
                'history' => $this->aysa_agent8v3_onpage_content_rewrite_history_rows(),
                'billing_status' => 'not_billable_read_only',
                'billing_cost_cents' => 0,
                'actual_credits_cents' => 0,
                'writes_enabled' => false,
            ));
        }
        if ($operation === 'restore') {
            wp_send_json_success($this->aysa_agent8v3_onpage_content_rewrite_restore_bridge($project_id, $history_id));
        }
        wp_send_json_error(array('message' => 'Unsupported Content Rewrite history operation.'), 400);
    }

    private function aysa_agent8v3_onpage_content_rewrite_history_rows() {
        $store = get_option('aysa_agent8v3_onpage_content_rewrite_restore_store', array());
        $store = is_array($store) ? $store : array();
        $rows = array();
        foreach ($store as $row) {
            if (!is_array($row) || empty($row['history_id'])) {
                continue;
            }
            $before_hash = sanitize_text_field((string) ($row['before_hash'] ?? $row['current_hash'] ?? ''));
            $after_hash = sanitize_text_field((string) ($row['after_hash'] ?? $row['proposed_hash'] ?? ''));
            $target = sanitize_key((string) ($row['target'] ?? ''));
            $entity_id = intval($row['entity_id'] ?? 0);
            $item = array(
                'entity_key' => sanitize_text_field((string) ($row['entity_key'] ?? '')),
                'entity_id' => $entity_id,
                'object_kind' => sanitize_key((string) ($row['object_kind'] ?? 'post')),
                'taxonomy' => sanitize_key((string) ($row['taxonomy'] ?? '')),
                'content_key' => sanitize_key((string) ($row['content_key'] ?? '')),
                'proposed_value' => (string) ($row['proposed_value'] ?? ''),
            );
            $readback = $target !== '' ? $this->aysa_agent8v3_onpage_content_rewrite_resolve_target_values(intval($row['project_id'] ?? 0), $item, $target) : array();
            $current_hash = sanitize_text_field((string) ($readback['current_hash'] ?? ''));
            $restored_at = sanitize_text_field((string) ($row['restored_at'] ?? ''));
            $restore_status = sanitize_key((string) ($row['restore_status'] ?? ($restored_at !== '' ? 'verified' : 'available')));
            $restore_available = !empty($row['restore_available'])
                && $restored_at === ''
                && sanitize_key((string) ($row['verification_status'] ?? '')) === 'verified'
                && $before_hash !== ''
                && $after_hash !== ''
                && $current_hash !== ''
                && hash_equals($after_hash, $current_hash);
            $rows[] = array(
                'history_id' => sanitize_text_field((string) ($row['history_id'] ?? '')),
                'project_id' => intval($row['project_id'] ?? 0),
                'entity_key' => sanitize_text_field((string) ($row['entity_key'] ?? '')),
                'entity_id' => $entity_id,
                'target' => $target,
                'content_surface' => sanitize_key((string) ($row['content_surface'] ?? '')),
                'status' => sanitize_key((string) ($row['status'] ?? 'unknown')),
                'verification_status' => sanitize_key((string) ($row['verification_status'] ?? 'unknown')),
                'billing_status' => sanitize_key((string) ($row['billing_status'] ?? 'unknown')),
                'billing_cost_cents' => intval($row['billing_cost_cents'] ?? 0),
                'restore_status' => $restore_status,
                'restore_available' => $restore_available,
                'restore_billing_status' => sanitize_key((string) ($row['restore_billing_status'] ?? 'not_billable_restore')),
                'restore_actual_credits_cents' => intval($row['restore_actual_credits_cents'] ?? 0),
                'restored_at' => $restored_at,
                'timestamp' => sanitize_text_field((string) ($row['timestamp'] ?? '')),
                'updated_at' => sanitize_text_field((string) ($row['updated_at'] ?? '')),
                'before_hash' => $before_hash,
                'after_hash' => $after_hash,
                'current_hash' => $current_hash,
                'current_hash_matches_after' => $after_hash !== '' && $current_hash !== '' && hash_equals($after_hash, $current_hash),
                'current_hash_matches_before' => $before_hash !== '' && $current_hash !== '' && hash_equals($before_hash, $current_hash),
                'current_preview' => mb_substr(trim(preg_replace('/\s+/u', ' ', wp_strip_all_tags((string) ($readback['current_value'] ?? ''))) ?: ''), 0, 160),
                'old_preview' => mb_substr(trim(preg_replace('/\s+/u', ' ', wp_strip_all_tags((string) ($row['current_value'] ?? ''))) ?: ''), 0, 160),
                'new_preview' => mb_substr(trim(preg_replace('/\s+/u', ' ', wp_strip_all_tags((string) ($row['proposed_value'] ?? ''))) ?: ''), 0, 160),
            );
        }
        usort($rows, static function($a, $b) {
            return strcmp((string) ($b['updated_at'] ?? ''), (string) ($a['updated_at'] ?? ''));
        });
        return array_slice($rows, 0, 50);
    }

    private function aysa_agent8v3_onpage_content_rewrite_find_history_row($history_id) {
        $history_id = sanitize_text_field((string) $history_id);
        $store = get_option('aysa_agent8v3_onpage_content_rewrite_restore_store', array());
        $store = is_array($store) ? $store : array();
        foreach ($store as $key => $row) {
            if (is_array($row) && sanitize_text_field((string) ($row['history_id'] ?? '')) === $history_id) {
                return array($key, $row);
            }
        }
        return array('', array());
    }

    private function aysa_agent8v3_onpage_content_rewrite_restore_bridge($project_id, $history_id) {
        $project_id = intval($project_id);
        $history_id = sanitize_text_field((string) $history_id);
        $result = array(
            'operation' => 'onpage_content_rewrite_restore',
            'module' => 'onpage_content_rewrite',
            'history_id' => $history_id,
            'status' => 'failed',
            'billing_status' => 'not_billable_restore',
            'billing_cost_cents' => 0,
            'actual_credits_cents' => 0,
            'writes_enabled' => true,
            'billing_enabled' => false,
            'max_selected_rows' => 1,
        );
        if ($history_id === '') {
            return array_merge($result, array('reason' => 'history_id_required'));
        }

        list($store_key, $row) = $this->aysa_agent8v3_onpage_content_rewrite_find_history_row($history_id);
        if ($store_key === '' || !is_array($row) || $row === array()) {
            return array_merge($result, array('reason' => 'history_not_found'));
        }

        $target = sanitize_key((string) ($row['target'] ?? ''));
        $entity_id = intval($row['entity_id'] ?? 0);
        $before_hash = sanitize_text_field((string) ($row['before_hash'] ?? $row['current_hash'] ?? ''));
        $after_hash = sanitize_text_field((string) ($row['after_hash'] ?? $row['proposed_hash'] ?? ''));
        $old_value = (string) ($row['current_value'] ?? '');
        if ($project_id <= 0) {
            $project_id = intval($row['project_id'] ?? 0);
        }
        if ($entity_id <= 0 || !in_array($target, array('post_content', 'post_excerpt', 'term_description'), true)) {
            return array_merge($result, array('reason' => 'target_not_supported'));
        }
        if ($before_hash === '' || $after_hash === '' || $old_value === '') {
            return array_merge($result, array('reason' => 'restore_snapshot_incomplete'));
        }
        if (sanitize_key((string) ($row['verification_status'] ?? '')) !== 'verified') {
            return array_merge($result, array('reason' => 'source_apply_not_verified'));
        }

        $item = array(
            'entity_key' => sanitize_text_field((string) ($row['entity_key'] ?? '')),
            'entity_id' => $entity_id,
            'object_kind' => sanitize_key((string) ($row['object_kind'] ?? 'post')),
            'taxonomy' => sanitize_key((string) ($row['taxonomy'] ?? '')),
            'content_key' => sanitize_key((string) ($row['content_key'] ?? '')),
            'proposed_value' => (string) ($row['proposed_value'] ?? ''),
        );
        $readback_before = $this->aysa_agent8v3_onpage_content_rewrite_resolve_target_values($project_id, $item, $target);
        if ((string) ($readback_before['status'] ?? '') !== 'read_current_target') {
            return array_merge($result, array('reason' => sanitize_key((string) ($readback_before['status'] ?? 'target_not_read'))));
        }
        $current_hash = sanitize_text_field((string) ($readback_before['current_hash'] ?? ''));
        if ($current_hash !== '' && hash_equals($before_hash, $current_hash)) {
            $this->aysa_agent8v3_update_content_rewrite_history_row($history_id, array(
                'restore_status' => 'already_restored',
                'restored_at' => current_time('mysql'),
                'restore_actual_hash' => $current_hash,
                'restore_billing_status' => 'not_billable_restore',
                'restore_actual_credits_cents' => 0,
                'restore_reference' => 'onpage_content_rewrite_restore:' . $history_id,
            ));
            return array_merge($result, array(
                'status' => 'already_restored',
                'verification_status' => 'verified',
                'actual_hash' => $current_hash,
                'writes_enabled' => false,
                'completed_at' => current_time('mysql'),
            ));
        }
        if ($current_hash === '' || !hash_equals($after_hash, $current_hash)) {
            return array_merge($result, array(
                'status' => 'blocked',
                'reason' => 'current_hash_conflict',
                'expected_current_hash' => $after_hash,
                'actual_hash' => $current_hash,
                'writes_enabled' => false,
            ));
        }

        if ($target === 'term_description') {
            $taxonomy = sanitize_key((string) ($row['taxonomy'] ?? $readback_before['taxonomy'] ?? ''));
            $write = $taxonomy !== '' ? wp_update_term($entity_id, $taxonomy, array('description' => wp_kses_post($old_value))) : new WP_Error('taxonomy_missing');
        } elseif ($target === 'post_excerpt') {
            $write = wp_update_post(array('ID' => $entity_id, 'post_excerpt' => wp_slash($old_value)), true);
        } else {
            $write = wp_update_post(array('ID' => $entity_id, 'post_content' => wp_slash($old_value)), true);
        }
        if (is_wp_error($write)) {
            $this->aysa_agent8v3_update_content_rewrite_history_row($history_id, array(
                'restore_status' => 'failed',
                'restore_error_code' => $write->get_error_code(),
                'restore_billing_status' => 'not_billable_restore',
                'restore_actual_credits_cents' => 0,
            ));
            return array_merge($result, array('status' => 'failed', 'reason' => $write->get_error_code()));
        }

        $readback_after = $this->aysa_agent8v3_onpage_content_rewrite_resolve_target_values($project_id, $item, $target);
        $actual_hash = sanitize_text_field((string) ($readback_after['current_hash'] ?? ''));
        $verified = $actual_hash !== '' && hash_equals($before_hash, $actual_hash);
        $this->aysa_agent8v3_update_content_rewrite_history_row($history_id, array(
            'restore_status' => $verified ? 'verified' : 'failed',
            'restored_at' => $verified ? current_time('mysql') : '',
            'restore_actual_hash' => $actual_hash,
            'restore_error_code' => $verified ? '' : 'restore_readback_mismatch',
            'restore_billing_status' => 'not_billable_restore',
            'restore_actual_credits_cents' => 0,
            'restore_reference' => 'onpage_content_rewrite_restore:' . $history_id,
        ));
        return array_merge($result, array(
            'status' => $verified ? 'verified' : 'failed',
            'verification_status' => $verified ? 'verified' : 'failed',
            'entity_id' => $entity_id,
            'entity_key' => sanitize_text_field((string) ($row['entity_key'] ?? '')),
            'target' => $target,
            'expected_hash' => $before_hash,
            'actual_hash' => $actual_hash,
            'error_code' => $verified ? '' : 'restore_readback_mismatch',
            'completed_at' => current_time('mysql'),
        ));
    }

    private function aysa_agent8v3_onpage_content_rewrite_apply_controlled_bridge($payload) {
        $payload = is_array($payload) ? $payload : array();
        $project_id = intval($payload['wp_project_id'] ?? get_option('aysa_project_id', 0));
        $accepted_items = isset($payload['accepted_items']) && is_array($payload['accepted_items'])
            ? array_values(array_filter($payload['accepted_items'], 'is_array'))
            : (isset($payload['payload']['accepted_items']) && is_array($payload['payload']['accepted_items'])
                ? array_values(array_filter($payload['payload']['accepted_items'], 'is_array'))
                : array());
        $gate_config = $this->aysa_agent8v3_onpage_content_rewrite_controlled_gate_config($payload, count($accepted_items));
        $preflight_enforcement = $this->aysa_agent8v3_onpage_content_rewrite_preflight_enforcement($payload, $accepted_items, $gate_config);
        $single_row_contract = $this->aysa_agent8v3_onpage_content_rewrite_single_row_contract($payload, $accepted_items, $gate_config, $preflight_enforcement);
        $item_results = array();
        $allowed_targets = array('post_content', 'post_excerpt', 'term_description');
        foreach ($accepted_items as $item) {
            $item = is_array($item) ? $item : array();
            $entity_key = sanitize_text_field((string) ($item['entity_key'] ?? $item['item_id'] ?? ''));
            $target = sanitize_key((string) ($item['target'] ?? $item['field'] ?? 'post_content'));
            $before_hash = sanitize_text_field((string) ($item['before_hash'] ?? $item['current_hash'] ?? $item['content_before_hash'] ?? ''));
            $after_hash = sanitize_text_field((string) ($item['after_hash'] ?? $item['proposed_hash'] ?? $item['content_after_hash'] ?? ''));
            $surface = sanitize_key((string) ($item['content_surface'] ?? $item['surface'] ?? 'unknown'));
            $history_readback_contract = $this->aysa_agent8v3_onpage_content_rewrite_history_readback_contract($item, $target, $before_hash, $after_hash, $surface);
            $executor_shell = $this->aysa_agent8v3_onpage_content_rewrite_gated_write_executor_shell($item, $target, $before_hash, $after_hash, $surface);
            $blocking_reasons = is_array($preflight_enforcement['blocking_reasons'] ?? null) ? $preflight_enforcement['blocking_reasons'] : array();
            if (empty($preflight_enforcement['passed'])) {
                $blocking_reasons[] = 'content_rewrite_apply_gate_off';
            }
            if ($entity_key === '') {
                $blocking_reasons[] = 'entity_key_required';
            }
            if (!in_array($target, $allowed_targets, true)) {
                $blocking_reasons[] = 'target_not_allowlisted';
            }
            if ($before_hash === '' || $after_hash === '') {
                $blocking_reasons[] = 'before_after_hash_required';
            }
            if ($before_hash !== '' && $after_hash !== '' && hash_equals($before_hash, $after_hash)) {
                $blocking_reasons[] = 'no_content_change';
            }
            if (in_array($surface, array('elementor', 'wpbakery', 'divi', 'unknown'), true)) {
                $blocking_reasons[] = 'unsupported_content_surface';
            }
            $item_results[] = array(
                'item_id' => sanitize_text_field((string) ($item['item_id'] ?? $entity_key)),
                'entity_key' => $entity_key,
                'target' => $target,
                'content_surface' => $surface,
                'before_hash' => $before_hash,
                'after_hash' => $after_hash,
                'status' => 'skipped',
                'reason' => 'content_rewrite_apply_gate_off',
                'writes_enabled' => false,
                'billing_enabled' => false,
                'history_snapshot' => $history_readback_contract['history_snapshot'],
                'readback_contract' => $history_readback_contract['readback_contract'],
                'restore_contract' => $history_readback_contract['restore_contract'],
                'billing_candidate' => $history_readback_contract['billing_candidate'],
                'write_executor_shell' => $executor_shell['write_executor_shell'],
                'readback_shell' => $executor_shell['readback_shell'],
                'restore_execution_shell' => $executor_shell['restore_execution_shell'],
                'blocking_reasons' => array_values(array_unique($blocking_reasons)),
            );
        }
        $payload_hash = sanitize_text_field((string) ($payload['payload_hash'] ?? ''));
        if ($payload_hash === '') {
            $encoded_items = wp_json_encode($accepted_items);
            $payload_hash = hash('sha256', is_string($encoded_items) ? $encoded_items : maybe_serialize($accepted_items));
        }
        $idempotency_key = sanitize_text_field((string) ($payload['idempotency_key'] ?? ''));
        if ($idempotency_key === '') {
            $idempotency_key = 'agent8v3:onpage_content_rewrite_controlled_apply:' . substr($payload_hash, 0, 32);
        }
        $items_hash = hash('sha256', wp_json_encode($accepted_items));
        $records = get_option('aysa_agent8v3_onpage_content_rewrite_apply_idempotency', array());
        $records = is_array($records) ? $records : array();
        if (isset($records[$idempotency_key]) && is_array($records[$idempotency_key])) {
            $record = $records[$idempotency_key];
            if (sanitize_text_field((string) ($record['items_hash'] ?? '')) !== $items_hash) {
                return array(
                    'code' => 'onpage_content_rewrite_apply_controlled_bridge',
                    'status' => 'idempotency_conflict',
                    'reason' => 'idempotency_key_payload_mismatch',
                    'writes_enabled' => false,
                    'billing_enabled' => false,
                    'accepted_count' => count($accepted_items),
                    'applied_count' => 0,
                    'verified_count' => 0,
                    'failed_count' => 1,
                    'skipped_count' => 0,
                    'actual_credits_cents' => 0,
                );
            }
            $replay = is_array($record['result'] ?? null) ? $record['result'] : array();
            $replay['idempotent_replay'] = true;
            return $replay;
        }

        if (!empty($preflight_enforcement['passed']) && !empty($gate_config['writes_enabled'])) {
            $unit_cost_cents = max(1, intval($payload['billing_unit_cents'] ?? 5));
            $max_cost_cents = max(count($accepted_items) * $unit_cost_cents, intval($payload['max_cost_cents'] ?? 0));
            $billing_preflight = $this->aysa_agent8v3_image_alt_test_billing_preflight($max_cost_cents, $idempotency_key, 'content_rewrite_agent8_flow');
            if (empty($billing_preflight['ok'])) {
                return array(
                    'code' => 'onpage_content_rewrite_apply_controlled_bridge',
                    'message' => 'Content Rewrite apply a fost blocat înainte de write: credit gate/billing preflight nu a trecut.',
                    'write_class' => 'onpage_content_rewrite_apply',
                    'operation' => 'onpage_content_rewrite_apply_bridge',
                    'status' => 'blocked',
                    'reason' => 'content_rewrite_billing_preflight_failed',
                    'gate_reasons' => array('content_rewrite_billing_preflight_failed'),
                    'writes_enabled' => false,
                    'billing_enabled' => false,
                    'billing_status' => 'billing_preflight_failed',
                    'billing_preflight' => $billing_preflight,
                    'accepted_count' => count($accepted_items),
                    'applied_count' => 0,
                    'verified_count' => 0,
                    'failed_count' => 0,
                    'skipped_count' => count($accepted_items),
                    'actual_credits_cents' => 0,
                    'idempotency_key' => $idempotency_key,
                    'payload_hash' => $payload_hash,
                );
            }

            $live_result = array(
                'code' => 'onpage_content_rewrite_apply_controlled_bridge',
                'message' => 'Content Rewrite single-row apply a rulat prin gate scoped.',
                'write_class' => 'onpage_content_rewrite_apply',
                'operation' => 'onpage_content_rewrite_apply_bridge',
                'status' => 'failed',
                'dry_run' => false,
                'writes_enabled' => true,
                'billing_enabled' => true,
                'billing_status' => 'content_rewrite_real_billing',
                'billing_action_key' => 'agent8v3_onpage_content_rewrite_apply',
                'billing_policy' => 'verified_changed_content_targets_only_cost_x5',
                'billing_unit_cents' => $unit_cost_cents,
                'billing_cost_cents' => 0,
                'actual_credits_cents' => 0,
                'worker_task_group' => 'onpage_apply_content_rewrite_controlled_single_row',
                'plugin_bridge' => 'onpage_content_rewrite_apply_controlled_bridge',
                'apply_gate_status' => 'scoped_on',
                'controlled_apply_status' => 'scoped_write_executed',
                'controlled_apply_ready' => true,
                'gate_config' => $gate_config,
                'preflight_enforcement' => $preflight_enforcement,
                'single_row_contract' => $single_row_contract,
                'accepted_count' => count($accepted_items),
                'applied_count' => 0,
                'verified_count' => 0,
                'failed_count' => 0,
                'skipped_count' => 0,
                'item_results' => array(),
                'billing_preflight' => $billing_preflight,
                'idempotency_key' => $idempotency_key,
                'payload_hash' => $payload_hash,
            );
            $context = array(
                'project_id' => $project_id,
                'site_host' => $this->aysa_agent8v3_site_host(),
                'payload_hash' => $payload_hash,
                'idempotency_key' => $idempotency_key,
                'source_run_id' => intval($payload['run_id'] ?? 0),
            );
            foreach ($accepted_items as $item) {
                $item_result = $this->aysa_agent8v3_apply_single_onpage_content_rewrite_item($project_id, $item, $context);
                $live_result['item_results'][] = $item_result;
                $status = sanitize_key((string) ($item_result['status'] ?? 'failed'));
                if ($status === 'verified') {
                    $live_result['applied_count']++;
                    $live_result['verified_count']++;
                } elseif (str_starts_with($status, 'skipped')) {
                    $live_result['skipped_count']++;
                } elseif ($status === 'manual_review_needed') {
                    $live_result['skipped_count']++;
                } else {
                    $live_result['failed_count']++;
                }
            }
            if ($live_result['verified_count'] > 0 && $live_result['failed_count'] === 0) {
                $live_result['status'] = 'verified';
            } elseif ($live_result['verified_count'] > 0) {
                $live_result['status'] = 'partial_failed';
            } elseif ($live_result['skipped_count'] > 0 && $live_result['failed_count'] === 0) {
                $live_result['status'] = 'skipped';
            } else {
                $live_result['status'] = 'failed';
            }
            $billing_result = $this->aysa_agent8v3_bill_content_rewrite_apply_result($project_id, $payload, $live_result);
            $this->aysa_agent8v3_update_content_rewrite_history_billing(is_array($live_result['item_results'] ?? null) ? $live_result['item_results'] : array(), $billing_result);
            $live_result['billing'] = $billing_result;
            $live_result['billing_status'] = sanitize_key((string) ($billing_result['status'] ?? 'billing_not_applicable'));
            $live_result['billing_cost_cents'] = intval($billing_result['cost_cents'] ?? 0);
            $live_result['actual_credits_cents'] = intval($billing_result['cost_cents'] ?? 0);
            $live_result['completed_at'] = current_time('mysql');
            $records[$idempotency_key] = array(
                'items_hash' => $items_hash,
                'result' => $live_result,
                'timestamp' => current_time('mysql'),
            );
            update_option('aysa_agent8v3_onpage_content_rewrite_apply_idempotency', array_slice($records, -100, null, true), false);
            return $live_result;
        }
        $restore_store_results = $this->aysa_agent8v3_onpage_content_rewrite_persist_restore_store($payload_hash, $idempotency_key, $item_results);
        foreach ($item_results as $index => $item_result) {
            $store_key = sanitize_text_field((string) ($item_result['item_id'] ?? $item_result['entity_key'] ?? $index));
            $store_result = $restore_store_results[$store_key] ?? array();
            if ($store_result !== array()) {
                $item_results[$index]['restore_store'] = $store_result;
                if (isset($item_results[$index]['history_snapshot']) && is_array($item_results[$index]['history_snapshot'])) {
                    $item_results[$index]['history_snapshot']['status'] = 'gate_off_snapshot_persisted';
                    $item_results[$index]['history_snapshot']['persisted'] = true;
                    $item_results[$index]['history_snapshot']['store_key'] = sanitize_text_field((string) ($store_result['store_key'] ?? ''));
                }
                if (isset($item_results[$index]['restore_contract']) && is_array($item_results[$index]['restore_contract'])) {
                    $item_results[$index]['restore_contract']['store_available'] = true;
                    $item_results[$index]['restore_contract']['store_key'] = sanitize_text_field((string) ($store_result['store_key'] ?? ''));
                }
            }
        }
        $controlled_readiness = $this->aysa_agent8v3_onpage_content_rewrite_controlled_apply_readiness(count($accepted_items));

        return array(
            'code' => 'onpage_content_rewrite_apply_controlled_bridge',
            'message' => 'Content Rewrite apply este pregătit ca fundație controlată, dar gate-ul este OFF. Nu se scrie conținut și nu se debitează credite.',
            'write_class' => 'onpage_content_rewrite_apply',
            'operation' => 'onpage_content_rewrite_apply_bridge',
            'status' => 'gate_off_no_write',
            'dry_run' => false,
            'writes_enabled' => false,
            'billing_enabled' => false,
            'billing_status' => 'not_billable_gate_off',
            'billing_cost_cents' => 0,
            'actual_credits_cents' => 0,
            'worker_task_group' => 'onpage_apply_content_rewrite_controlled_gate_off',
            'plugin_bridge' => 'onpage_content_rewrite_apply_controlled_bridge',
            'apply_gate_status' => 'off',
            'controlled_apply_status' => 'gate_off_no_write',
            'controlled_apply_ready' => false,
            'controlled_apply_readiness' => $controlled_readiness,
            'gate_config' => $gate_config,
            'preflight_enforcement' => $preflight_enforcement,
            'single_row_contract' => $single_row_contract,
            'history_readback_foundation' => $this->aysa_agent8v3_onpage_content_rewrite_history_readback_foundation(count($accepted_items)),
            'write_executor_shell' => array(
                'status' => 'gate_off_no_write',
                'available' => true,
                'writes_enabled' => false,
                'readback_enabled' => false,
                'restore_enabled' => false,
                'billing_enabled' => false,
            ),
            'restore_store' => array(
                'option_key' => 'aysa_agent8v3_onpage_content_rewrite_restore_store',
                'persisted_count' => count($restore_store_results),
                'writes_enabled' => false,
                'billing_enabled' => false,
            ),
            'write_execution_status' => 'blocked_until_controlled_gate',
            'live_write_requires_separate_confirmation' => true,
            'write_checkpoint' => array(
                'status' => 'requires_explicit_write_confirmation',
                'stop_before_write' => true,
                'final_confirmation_required' => true,
                'write_may_run_in_this_wave' => false,
            ),
            'accepted_count' => count($accepted_items),
            'skipped_count' => count($accepted_items),
            'applied_count' => 0,
            'verified_count' => 0,
            'idempotency_key' => $idempotency_key,
            'payload_hash' => $payload_hash,
            'item_results' => $item_results,
            'required_before_enable' => array(
                'api_gate_on',
                'plugin_site_gate_on',
                'explicit_selected_rows',
                'worker_task_write_execution',
                'plugin_bridge_write_allowlist',
                'before_after_content_history',
                'readback_verification',
                'hash_conflict_safe_restore',
                'verified_only_billing',
                'controlled_live_test',
            ),
        );
    }

    private function aysa_agent8v3_onpage_content_rewrite_controlled_gate_config($payload, $selected_count) {
        $payload = is_array($payload) ? $payload : array();
        $selected_count = max(0, intval($selected_count));
        $confirmation = sanitize_text_field((string) ($payload['confirmation_kind'] ?? $payload['confirmed_kind'] ?? ''));
        $payload_product_gates = isset($payload['product_gates']) && is_array($payload['product_gates']) ? $payload['product_gates'] : array();
        $local_product_gates = $this->aysa_agent8v3_onpage_content_rewrite_product_gates();
        $item = is_array($payload['payload']['accepted_items'][0] ?? null) ? $payload['payload']['accepted_items'][0] : (is_array($payload['accepted_items'][0] ?? null) ? $payload['accepted_items'][0] : array());
        $entity_key = sanitize_text_field((string) ($item['entity_key'] ?? $item['item_id'] ?? ''));
        $target = sanitize_key((string) ($item['target'] ?? $item['field'] ?? ''));
        $payload_hash = sanitize_text_field((string) ($payload['payload_hash'] ?? ''));
        $idempotency_key = sanitize_text_field((string) ($payload['idempotency_key'] ?? ''));
        $scope = is_array($local_product_gates['scoped_gate'] ?? null) ? $local_product_gates['scoped_gate'] : array();
        $scope_matches = !empty($scope['enabled'])
            && $entity_key !== ''
            && $target !== ''
            && $payload_hash !== ''
            && $idempotency_key !== ''
            && hash_equals(sanitize_text_field((string) ($scope['entity_key'] ?? '')), $entity_key)
            && hash_equals(sanitize_key((string) ($scope['target'] ?? '')), $target)
            && hash_equals(sanitize_text_field((string) ($scope['payload_hash'] ?? '')), $payload_hash)
            && hash_equals(sanitize_text_field((string) ($scope['idempotency_key'] ?? '')), $idempotency_key);
        $api_gate_on = !empty($payload_product_gates['project_gate_enabled']) && !empty($local_product_gates['project_gate_enabled']);
        $plugin_site_gate_on = !empty($payload_product_gates['site_gate_enabled']) && !empty($local_product_gates['site_gate_enabled']);
        $kill_switch_active = !empty($payload_product_gates['global_kill_switch_active']) || !empty($local_product_gates['global_kill_switch_active']);
        $final_confirmed = !empty($payload['op26_final_write_confirmed']) && !empty($payload['confirmed_by_user']);
        $billing_status = sanitize_key((string) ($payload['billing_status'] ?? ''));
        $billing_action_key = sanitize_key((string) ($payload['billing_action_key'] ?? ''));
        $writes_requested = !empty($payload['writes_enabled']) && empty($payload['dry_run']);
        $writes_enabled = $api_gate_on
            && $plugin_site_gate_on
            && !$kill_switch_active
            && $scope_matches
            && $final_confirmed
            && $writes_requested
            && $billing_status === 'content_rewrite_real_billing'
            && $billing_action_key === 'agent8v3_onpage_content_rewrite_apply';
        return array(
            'status' => $writes_enabled ? 'scoped_gate_on' : 'gate_off',
            'api_gate_on' => $api_gate_on,
            'plugin_site_gate_on' => $plugin_site_gate_on,
            'kill_switch_off' => !$kill_switch_active,
            'scoped_gate_matches' => $scope_matches,
            'op26_final_write_confirmed' => $final_confirmed,
            'writes_requested' => $writes_requested,
            'writes_enabled' => $writes_enabled,
            'billing_enabled' => $writes_enabled,
            'billing_status' => $billing_status,
            'billing_action_key' => $billing_action_key,
            'max_selected_rows' => 1,
            'selected_rows' => $selected_count,
            'selected_rows_within_limit' => $selected_count > 0 && $selected_count <= 1,
            'required_confirmation_kind' => 'onpage_content_rewrite_apply',
            'confirmation_kind' => $confirmation,
            'confirmation_valid' => $confirmation === 'onpage_content_rewrite_apply',
            'target_allowlist' => array(
                'post_content',
                'post_excerpt',
                'term_description',
            ),
            'safe_target_required' => true,
            'staging_or_explicit_safe_target_required' => true,
            'controlled_live_write_approval_required' => true,
            'scoped_gate_model' => array(
                'status' => $writes_enabled ? 'scoped_gate_on' : 'foundation_only_gate_off',
                'scope_required' => 'project_site_entity_target_idempotency',
                'gate_can_enable_only_after_preflight' => true,
                'default_gate_state' => 'off',
                'broad_gate_allowed' => false,
                'scope_matches' => $scope_matches,
            ),
        );
    }

    private function aysa_agent8v3_onpage_content_rewrite_preflight_enforcement($payload, $accepted_items, $gate_config) {
        $payload = is_array($payload) ? $payload : array();
        $accepted_items = is_array($accepted_items) ? $accepted_items : array();
        $gate_config = is_array($gate_config) ? $gate_config : array();
        $blocking = array();
        if (empty($gate_config['api_gate_on'])) {
            $blocking[] = 'api_gate_off';
        }
        if (empty($gate_config['plugin_site_gate_on'])) {
            $blocking[] = 'plugin_site_gate_off';
        }
        if (empty($gate_config['kill_switch_off'])) {
            $blocking[] = 'kill_switch_not_confirmed_off';
        }
        if (empty($gate_config['confirmation_valid'])) {
            $blocking[] = 'confirmation_kind_required';
        }
        if (empty($gate_config['selected_rows_within_limit'])) {
            $blocking[] = 'selected_rows_outside_limit';
        }
        if (empty($gate_config['scoped_gate_matches'])) {
            $blocking[] = 'scoped_gate_mismatch';
        }
        if (empty($gate_config['op26_final_write_confirmed'])) {
            $blocking[] = 'final_write_confirmation_required';
        }
        if (empty($gate_config['writes_requested'])) {
            $blocking[] = 'writes_not_requested';
        }
        if (sanitize_key((string) ($gate_config['billing_status'] ?? '')) !== 'content_rewrite_real_billing') {
            $blocking[] = 'content_rewrite_real_billing_required';
        }
        if (sanitize_key((string) ($gate_config['billing_action_key'] ?? '')) !== 'agent8v3_onpage_content_rewrite_apply') {
            $blocking[] = 'content_rewrite_billing_action_required';
        }
        $allowed_targets = is_array($gate_config['target_allowlist'] ?? null) ? $gate_config['target_allowlist'] : array();
        foreach ($accepted_items as $item) {
            $item = is_array($item) ? $item : array();
            $target = sanitize_key((string) ($item['target'] ?? $item['field'] ?? 'post_content'));
            $surface = sanitize_key((string) ($item['content_surface'] ?? $item['surface'] ?? 'unknown'));
            if (!in_array($target, $allowed_targets, true)) {
                $blocking[] = 'target_not_allowlisted';
            }
            if (in_array($surface, array('elementor', 'wpbakery', 'divi', 'unknown'), true)) {
                $blocking[] = 'unsupported_content_surface';
            }
            if (sanitize_text_field((string) ($item['before_hash'] ?? $item['current_hash'] ?? $item['content_before_hash'] ?? '')) === '') {
                $blocking[] = 'before_hash_required';
            }
            if (sanitize_text_field((string) ($item['after_hash'] ?? $item['proposed_hash'] ?? $item['content_after_hash'] ?? '')) === '') {
                $blocking[] = 'after_hash_required';
            }
        }
        return array(
            'status' => $blocking === array() && !empty($gate_config['writes_enabled']) ? 'passed' : 'blocked_gate_off',
            'passed' => $blocking === array() && !empty($gate_config['writes_enabled']),
            'writes_enabled' => $blocking === array() && !empty($gate_config['writes_enabled']),
            'billing_enabled' => $blocking === array() && !empty($gate_config['billing_enabled']),
            'blocking_reasons' => array_values(array_unique($blocking)),
            'requirements' => array(
                'api_gate_on',
                'plugin_site_gate_on',
                'kill_switch_off',
                'scoped_gate_matches',
                'op26_final_write_confirmed',
                'confirmation_kind',
                'selected_rows_within_limit',
                'target_allowlist',
                'supported_content_surface',
                'before_hash',
                'after_hash',
                'history_store_available',
                'readback_contract',
                'restore_store_available',
            ),
        );
    }

    private function aysa_agent8v3_onpage_content_rewrite_single_row_contract($payload, $accepted_items, $gate_config, $preflight_enforcement) {
        $payload = is_array($payload) ? $payload : array();
        $accepted_items = is_array($accepted_items) ? array_values(array_filter($accepted_items, 'is_array')) : array();
        $gate_config = is_array($gate_config) ? $gate_config : array();
        $preflight_enforcement = is_array($preflight_enforcement) ? $preflight_enforcement : array();
        $item = is_array($accepted_items[0] ?? null) ? $accepted_items[0] : array();
        $target = sanitize_key((string) ($item['target'] ?? $item['field'] ?? ''));
        $entity_key = sanitize_text_field((string) ($item['entity_key'] ?? $item['item_id'] ?? ''));
        $before_hash = sanitize_text_field((string) ($item['before_hash'] ?? $item['current_hash'] ?? $item['content_before_hash'] ?? ''));
        $after_hash = sanitize_text_field((string) ($item['after_hash'] ?? $item['proposed_hash'] ?? $item['content_after_hash'] ?? ''));
        $surface = sanitize_key((string) ($item['content_surface'] ?? $item['surface'] ?? 'unknown'));
        $allowlist = is_array($gate_config['target_allowlist'] ?? null) ? $gate_config['target_allowlist'] : array();
        $non_gate_blocking = array();
        if (count($accepted_items) !== 1) {
            $non_gate_blocking[] = 'exactly_one_selected_row_required';
        }
        if ($entity_key === '') {
            $non_gate_blocking[] = 'entity_identity_required';
        }
        if ($target === '' || !in_array($target, $allowlist, true)) {
            $non_gate_blocking[] = 'target_allowlist_required';
        }
        if ($before_hash === '') {
            $non_gate_blocking[] = 'before_hash_required';
        }
        if ($after_hash === '') {
            $non_gate_blocking[] = 'after_hash_required';
        }
        if ($before_hash !== '' && $after_hash !== '' && hash_equals($before_hash, $after_hash)) {
            $non_gate_blocking[] = 'changed_after_hash_required';
        }
        if (in_array($surface, array('elementor', 'wpbakery', 'divi', 'unknown'), true)) {
            $non_gate_blocking[] = 'supported_content_surface_required';
        }
        $preflight_passed = !empty($preflight_enforcement['passed']) && $non_gate_blocking === array();
        return array(
            'status' => $preflight_passed ? 'scoped_gate_ready' : 'gate_off_no_write',
            'candidate_ready_for_future_controlled_test' => $non_gate_blocking === array(),
            'apply_allowed' => $preflight_passed,
            'writes_enabled' => $preflight_passed,
            'billing_enabled' => $preflight_passed,
            'requires_final_write_confirmation' => true,
            'max_selected_rows' => 1,
            'max_cost_cents' => 5,
            'selected_rows' => count($accepted_items),
            'required_confirmation_kind' => 'onpage_content_rewrite_apply',
            'confirmation_valid' => !empty($gate_config['confirmation_valid']),
            'required_payload_fields' => array(
                'entity_key',
                'target',
                'content_surface',
                'before_hash',
                'after_hash',
                'confirmation_kind',
                'idempotency_key',
            ),
            'required_target_allowlist' => array(
                'post_content',
                'post_excerpt',
                'term_description',
            ),
            'selected_row_identity' => array(
                'entity_key' => $entity_key,
                'target' => $target,
                'content_surface' => $surface,
                'before_hash' => $before_hash,
                'after_hash' => $after_hash,
            ),
            'restore_requirements' => array(
                'history_store_available',
                'restore_snapshot_before_write',
                'restore_store_key_before_enable',
                'hash_conflict_guard_before_restore',
            ),
            'readback_requirements' => array(
                'read_current_target_before_write',
                'verify_after_hash_after_write',
                'block_billing_until_verified_changed',
            ),
            'scoped_gate_model' => array(
                'status' => $preflight_passed ? 'scoped_gate_on' : 'foundation_only_gate_off',
                'scope_required' => 'project_site_entity_target_idempotency',
                'gate_can_enable_only_after_preflight' => true,
                'default_gate_state' => 'off',
                'broad_gate_allowed' => false,
            ),
            'write_checkpoint' => array(
                'status' => $preflight_passed ? 'explicit_write_confirmed' : 'requires_explicit_write_confirmation',
                'stop_before_write' => !$preflight_passed,
                'final_confirmation_required' => true,
                'write_may_run_in_this_wave' => $preflight_passed,
            ),
            'non_gate_blocking_reasons' => array_values(array_unique($non_gate_blocking)),
            'gate_blocking_reasons' => is_array($preflight_enforcement['blocking_reasons'] ?? null) ? array_values($preflight_enforcement['blocking_reasons']) : array(),
            'next_enablement_step' => 'controlled_single_row_preflight_live_no_write_or_billing',
        );
    }

    private function aysa_agent8v3_onpage_content_rewrite_persist_restore_store($payload_hash, $idempotency_key, $item_results) {
        $item_results = is_array($item_results) ? $item_results : array();
        $store = get_option('aysa_agent8v3_onpage_content_rewrite_restore_store', array());
        $store = is_array($store) ? $store : array();
        $persisted = array();
        foreach ($item_results as $item_result) {
            if (!is_array($item_result)) {
                continue;
            }
            $item_id = sanitize_text_field((string) ($item_result['item_id'] ?? $item_result['entity_key'] ?? ''));
            $entity_key = sanitize_text_field((string) ($item_result['entity_key'] ?? $item_id));
            $target = sanitize_key((string) ($item_result['target'] ?? ''));
            if ($item_id === '' || $entity_key === '' || $target === '') {
                continue;
            }
            $store_key = hash('sha256', implode('|', array(
                sanitize_text_field((string) $idempotency_key),
                $item_id,
                $entity_key,
                $target,
            )));
            $entry = array(
                'status' => 'gate_off_snapshot_persisted',
                'store_key' => $store_key,
                'item_id' => $item_id,
                'entity_key' => $entity_key,
                'target' => $target,
                'content_surface' => sanitize_key((string) ($item_result['content_surface'] ?? '')),
                'payload_hash' => sanitize_text_field((string) $payload_hash),
                'idempotency_key' => sanitize_text_field((string) $idempotency_key),
                'before_hash' => sanitize_text_field((string) ($item_result['before_hash'] ?? '')),
                'after_hash' => sanitize_text_field((string) ($item_result['after_hash'] ?? '')),
                'history_snapshot' => is_array($item_result['history_snapshot'] ?? null) ? $item_result['history_snapshot'] : array(),
                'readback_contract' => is_array($item_result['readback_contract'] ?? null) ? $item_result['readback_contract'] : array(),
                'restore_contract' => is_array($item_result['restore_contract'] ?? null) ? $item_result['restore_contract'] : array(),
                'billing_candidate' => is_array($item_result['billing_candidate'] ?? null) ? $item_result['billing_candidate'] : array(),
                'writes_enabled' => false,
                'billing_enabled' => false,
                'created_at' => current_time('mysql'),
            );
            $entry['history_snapshot']['status'] = 'gate_off_snapshot_persisted';
            $entry['history_snapshot']['persisted'] = true;
            $entry['history_snapshot']['store_key'] = $store_key;
            $entry['restore_contract']['store_available'] = true;
            $entry['restore_contract']['store_key'] = $store_key;
            $store[$store_key] = $entry;
            $persisted[$item_id] = array(
                'status' => 'gate_off_snapshot_persisted',
                'store_key' => $store_key,
                'option_key' => 'aysa_agent8v3_onpage_content_rewrite_restore_store',
                'persisted' => true,
                'writes_enabled' => false,
                'billing_enabled' => false,
            );
        }
        if (count($store) > 200) {
            $store = array_slice($store, -200, null, true);
        }
        update_option('aysa_agent8v3_onpage_content_rewrite_restore_store', $store, false);
        return $persisted;
    }

    private function aysa_agent8v3_onpage_content_rewrite_gated_write_executor_shell($item, $target, $before_hash, $after_hash, $surface) {
        $item = is_array($item) ? $item : array();
        $entity_key = sanitize_text_field((string) ($item['entity_key'] ?? $item['item_id'] ?? ''));
        $current_read = $this->aysa_agent8v3_onpage_content_rewrite_read_current_target_shell($item, $target);
        $current_hash = sanitize_text_field((string) ($current_read['current_hash'] ?? ''));
        $before_hash_matches = $before_hash !== '' && $current_hash !== '' && hash_equals($before_hash, $current_hash);

        return array(
            'write_executor_shell' => array(
                'status' => 'gate_off_no_write',
                'entity_key' => $entity_key,
                'target' => $target,
                'content_surface' => $surface,
                'allowed_target' => in_array($target, array('post_content', 'post_excerpt', 'term_description'), true),
                'before_hash' => $before_hash,
                'after_hash' => $after_hash,
                'current_hash' => $current_hash,
                'before_hash_matches' => $before_hash_matches,
                'would_write_if_gate_on' => $before_hash_matches && $after_hash !== '' && !hash_equals($before_hash, $after_hash),
                'writes_enabled' => false,
                'billing_enabled' => false,
                'gate_status' => 'off',
            ),
            'readback_shell' => array(
                'status' => 'not_executed_gate_off',
                'target' => $target,
                'expected_after_hash' => $after_hash,
                'current_read' => $current_read,
                'verified' => false,
                'executed' => false,
            ),
            'restore_execution_shell' => array(
                'status' => 'not_executed_gate_off',
                'target' => $target,
                'restore_to_hash' => $before_hash,
                'expected_current_hash_before_restore' => $after_hash,
                'restore_allowed' => false,
                'non_billable' => true,
                'executed' => false,
            ),
        );
    }

    private function aysa_agent8v3_onpage_content_rewrite_read_current_target_shell($item, $target) {
        $item = is_array($item) ? $item : array();
        $entity_id = intval($item['entity_id'] ?? $item['post_id'] ?? $item['term_id'] ?? 0);
        $object_kind = sanitize_key((string) ($item['object_kind'] ?? $item['entity_type'] ?? 'post'));
        $value = '';
        $status = 'entity_id_missing';
        if ($entity_id > 0 && $target === 'term_description') {
            $term = get_term($entity_id);
            if ($term && !is_wp_error($term)) {
                $value = (string) ($term->description ?? '');
                $status = 'read_current_target';
            } else {
                $status = 'term_not_found';
            }
        } elseif ($entity_id > 0 && in_array($target, array('post_content', 'post_excerpt'), true)) {
            $post = get_post($entity_id);
            if ($post instanceof WP_Post) {
                $value = $target === 'post_excerpt' ? (string) $post->post_excerpt : (string) $post->post_content;
                $status = 'read_current_target';
            } else {
                $status = 'post_not_found';
            }
        } elseif ($object_kind === 'term' && $entity_id <= 0) {
            $status = 'term_id_missing';
        }
        $normalized = trim(preg_replace('/\s+/u', ' ', wp_strip_all_tags($value)) ?: '');
        return array(
            'status' => $status,
            'entity_id' => $entity_id,
            'target' => $target,
            'value_available' => $value !== '',
            'current_hash' => hash('sha256', $normalized),
            'read_only' => true,
        );
    }

    private function aysa_agent8v3_onpage_content_rewrite_normalized_hash($value) {
        $normalized = trim(preg_replace('/\s+/u', ' ', wp_strip_all_tags((string) $value)) ?: '');
        return hash('sha256', $normalized);
    }

    private function aysa_agent8v3_onpage_content_rewrite_resolve_target_values($project_id, $item, $target) {
        $project_id = intval($project_id);
        $item = is_array($item) ? $item : array();
        $entity_key = sanitize_text_field((string) ($item['entity_key'] ?? $item['item_id'] ?? ''));
        $target = sanitize_key((string) $target);
        $service = $this->aysa_onpage_workspace_service();
        $detail = ($service && method_exists($service, 'get_detail') && $project_id > 0 && $entity_key !== '')
            ? $service->get_detail($project_id, $entity_key)
            : array();
        $detail = is_array($detail) ? $detail : array();
        $inventory = is_array($detail['inventory'] ?? null) ? $detail['inventory'] : array();
        $rewrite_package = is_array($detail['rewrite_package'] ?? null) ? $detail['rewrite_package'] : array();

        $entity_id = intval($item['entity_id'] ?? $item['post_id'] ?? $item['term_id'] ?? $inventory['entity_id'] ?? 0);
        $object_kind = sanitize_key((string) ($item['object_kind'] ?? $item['entity_type'] ?? $inventory['object_kind'] ?? 'post'));
        $taxonomy = sanitize_key((string) ($item['taxonomy'] ?? $inventory['taxonomy'] ?? ''));
        $content_key = sanitize_key((string) ($item['content_key'] ?? ''));
        if ($content_key === '') {
            $content_key = $target === 'post_excerpt' ? 'short_description_html' : ($target === 'term_description' ? 'category_description_html' : 'rewritten_content');
        }

        $proposed = (string) ($item['proposed_value'] ?? $item['after_value'] ?? '');
        if ($proposed === '') {
            $proposed = (string) ($rewrite_package[$content_key] ?? '');
        }
        if ($proposed === '' && $target === 'post_content') {
            $proposed = (string) ($rewrite_package['long_description_html'] ?? $rewrite_package['rewritten_content'] ?? '');
        }
        if ($proposed === '' && $target === 'post_excerpt') {
            $proposed = (string) ($rewrite_package['content_description'] ?? '');
        }

        $current = '';
        $status = 'target_not_read';
        if ($entity_id > 0 && $target === 'term_description') {
            $term = get_term($entity_id);
            if ($term && !is_wp_error($term)) {
                $current = (string) ($term->description ?? '');
                $taxonomy = $taxonomy !== '' ? $taxonomy : sanitize_key((string) ($term->taxonomy ?? ''));
                $status = 'read_current_target';
            } else {
                $status = 'term_not_found';
            }
        } elseif ($entity_id > 0 && in_array($target, array('post_content', 'post_excerpt'), true)) {
            $post = get_post($entity_id);
            if ($post instanceof WP_Post) {
                $current = $target === 'post_excerpt' ? (string) $post->post_excerpt : (string) $post->post_content;
                $status = 'read_current_target';
            } else {
                $status = 'post_not_found';
            }
        }

        return array(
            'status' => $status,
            'entity_key' => $entity_key,
            'entity_id' => $entity_id,
            'object_kind' => $object_kind,
            'taxonomy' => $taxonomy,
            'target' => $target,
            'content_key' => $content_key,
            'current_value' => $current,
            'proposed_value' => $proposed,
            'current_hash' => $this->aysa_agent8v3_onpage_content_rewrite_normalized_hash($current),
            'proposed_hash' => $this->aysa_agent8v3_onpage_content_rewrite_normalized_hash($proposed),
        );
    }

    private function aysa_agent8v3_apply_single_onpage_content_rewrite_item($project_id, $item, $context = array()) {
        $project_id = intval($project_id);
        $item = is_array($item) ? $item : array();
        $context = is_array($context) ? $context : array();
        $entity_key = sanitize_text_field((string) ($item['entity_key'] ?? $item['item_id'] ?? ''));
        $target = sanitize_key((string) ($item['target'] ?? $item['field'] ?? ''));
        $before_hash = sanitize_text_field((string) ($item['before_hash'] ?? $item['current_hash'] ?? $item['content_before_hash'] ?? ''));
        $after_hash = sanitize_text_field((string) ($item['after_hash'] ?? $item['proposed_hash'] ?? $item['content_after_hash'] ?? ''));
        $surface = sanitize_key((string) ($item['content_surface'] ?? $item['surface'] ?? ''));
        $values = $this->aysa_agent8v3_onpage_content_rewrite_resolve_target_values($project_id, $item, $target);
        $item_result = array(
            'item_id' => sanitize_text_field((string) ($item['item_id'] ?? $entity_key)),
            'entity_key' => $entity_key,
            'entity_id' => intval($values['entity_id'] ?? 0),
            'target' => $target,
            'content_surface' => $surface,
            'before_hash' => $before_hash,
            'after_hash' => $after_hash,
            'actual_hash' => '',
            'changed' => false,
            'verification_status' => 'not_verified',
        );
        if ($project_id <= 0) {
            return array_merge($item_result, array('status' => 'failed', 'error_code' => 'project_id_required'));
        }
        if ($entity_key === '' || intval($values['entity_id'] ?? 0) <= 0) {
            return array_merge($item_result, array('status' => 'skipped_missing_item', 'skipped_reason' => 'entity_missing'));
        }
        if (!in_array($target, array('post_content', 'post_excerpt', 'term_description'), true)) {
            return array_merge($item_result, array('status' => 'manual_review_needed', 'skipped_reason' => 'target_not_allowlisted'));
        }
        if (in_array($surface, array('elementor', 'wpbakery', 'divi', 'unknown'), true)) {
            return array_merge($item_result, array('status' => 'manual_review_needed', 'skipped_reason' => 'unsupported_content_surface'));
        }
        if ((string) ($values['status'] ?? '') !== 'read_current_target') {
            return array_merge($item_result, array('status' => 'failed', 'error_code' => sanitize_key((string) ($values['status'] ?? 'target_not_read'))));
        }
        if ($before_hash === '' || $after_hash === '') {
            return array_merge($item_result, array('status' => 'failed', 'error_code' => 'before_after_hash_required'));
        }
        if (!hash_equals($before_hash, sanitize_text_field((string) ($values['current_hash'] ?? '')))) {
            return array_merge($item_result, array('status' => 'manual_review_needed', 'skipped_reason' => 'live_before_hash_differs', 'actual_hash' => sanitize_text_field((string) ($values['current_hash'] ?? ''))));
        }
        if (!hash_equals($after_hash, sanitize_text_field((string) ($values['proposed_hash'] ?? '')))) {
            return array_merge($item_result, array('status' => 'failed', 'error_code' => 'proposed_hash_mismatch', 'actual_hash' => sanitize_text_field((string) ($values['proposed_hash'] ?? ''))));
        }
        if (hash_equals($before_hash, $after_hash)) {
            return array_merge($item_result, array('status' => 'skipped_no_change', 'verification_status' => 'verified_no_change', 'actual_hash' => $before_hash));
        }

        $history_id = $this->aysa_agent8v3_create_content_rewrite_history_row($item, $context, $values);
        $item_result['history_id'] = $history_id;
        $entity_id = intval($values['entity_id'] ?? 0);
        $proposed_value = (string) ($values['proposed_value'] ?? '');
        $write = true;
        if ($target === 'term_description') {
            $taxonomy = sanitize_key((string) ($values['taxonomy'] ?? ''));
            $write = $taxonomy !== '' ? wp_update_term($entity_id, $taxonomy, array('description' => wp_kses_post($proposed_value))) : new WP_Error('taxonomy_missing');
        } elseif ($target === 'post_excerpt') {
            $write = wp_update_post(array('ID' => $entity_id, 'post_excerpt' => wp_slash($proposed_value)), true);
        } else {
            $write = wp_update_post(array('ID' => $entity_id, 'post_content' => wp_slash($proposed_value)), true);
        }
        if (is_wp_error($write)) {
            $this->aysa_agent8v3_update_content_rewrite_history_row($history_id, array('status' => 'failed', 'verification_status' => 'failed', 'error_code' => $write->get_error_code()));
            return array_merge($item_result, array('status' => 'failed', 'error_code' => $write->get_error_code()));
        }

        $readback = $this->aysa_agent8v3_onpage_content_rewrite_resolve_target_values($project_id, array_merge($item, array(
            'entity_id' => $entity_id,
            'object_kind' => sanitize_key((string) ($values['object_kind'] ?? 'post')),
            'taxonomy' => sanitize_key((string) ($values['taxonomy'] ?? '')),
        )), $target);
        $actual_hash = sanitize_text_field((string) ($readback['current_hash'] ?? ''));
        $verified = $actual_hash !== '' && hash_equals($after_hash, $actual_hash);
        $this->aysa_agent8v3_update_content_rewrite_history_row($history_id, array(
            'status' => $verified ? 'changed' : 'failed',
            'actual_hash' => $actual_hash,
            'verification_status' => $verified ? 'verified' : 'failed',
            'error_code' => $verified ? '' : 'verification_mismatch',
        ));
        return array_merge($item_result, array(
            'status' => $verified ? 'verified' : 'failed',
            'changed' => $verified,
            'verification_status' => $verified ? 'verified' : 'failed',
            'actual_hash' => $actual_hash,
            'error_code' => $verified ? '' : 'verification_mismatch',
        ));
    }

    private function aysa_agent8v3_create_content_rewrite_history_row($item, $context, $values) {
        $item = is_array($item) ? $item : array();
        $context = is_array($context) ? $context : array();
        $values = is_array($values) ? $values : array();
        $history_id = 'crh_' . time() . '_' . wp_generate_password(8, false, false);
        $store_key = hash('sha256', implode('|', array(
            sanitize_text_field((string) ($context['idempotency_key'] ?? '')),
            sanitize_text_field((string) ($item['entity_key'] ?? $item['item_id'] ?? '')),
            sanitize_key((string) ($values['target'] ?? '')),
            $history_id,
        )));
        $row = array(
            'status' => 'submitted',
            'source' => 'agent8v3_onpage_content_rewrite_apply',
            'history_id' => $history_id,
            'store_key' => $store_key,
            'project_id' => intval($context['project_id'] ?? 0),
            'site_host' => sanitize_text_field((string) ($context['site_host'] ?? $this->aysa_agent8v3_site_host())),
            'entity_key' => sanitize_text_field((string) ($item['entity_key'] ?? $item['item_id'] ?? '')),
            'entity_id' => intval($values['entity_id'] ?? 0),
            'object_kind' => sanitize_key((string) ($values['object_kind'] ?? ($item['object_kind'] ?? 'post'))),
            'taxonomy' => sanitize_key((string) ($values['taxonomy'] ?? ($item['taxonomy'] ?? ''))),
            'target' => sanitize_key((string) ($values['target'] ?? '')),
            'content_surface' => sanitize_key((string) ($item['content_surface'] ?? $item['surface'] ?? '')),
            'content_key' => sanitize_key((string) ($values['content_key'] ?? '')),
            'before_hash' => sanitize_text_field((string) ($item['before_hash'] ?? '')),
            'after_hash' => sanitize_text_field((string) ($item['after_hash'] ?? '')),
            'current_hash' => sanitize_text_field((string) ($values['current_hash'] ?? '')),
            'proposed_hash' => sanitize_text_field((string) ($values['proposed_hash'] ?? '')),
            'current_value' => wp_kses_post((string) ($values['current_value'] ?? '')),
            'proposed_value' => wp_kses_post((string) ($values['proposed_value'] ?? '')),
            'payload_hash' => sanitize_text_field((string) ($context['payload_hash'] ?? '')),
            'idempotency_key' => sanitize_text_field((string) ($context['idempotency_key'] ?? '')),
            'approval_token' => sanitize_text_field((string) ($item['approval_token'] ?? '')),
            'insertion_location_hash' => sanitize_text_field((string) ($item['insertion_location_hash'] ?? '')),
            'preview_before_content_hash' => sanitize_text_field((string) ($item['before_content_hash'] ?? '')),
            'preview_after_content_hash' => sanitize_text_field((string) ($item['after_content_hash'] ?? '')),
            'internal_link_classification' => sanitize_key((string) ($item['internal_link_classification'] ?? '')),
            'accepted_by_user' => !empty($item['accepted_by_user']),
            'accepted_by_user_id' => get_current_user_id(),
            'timestamp' => current_time('mysql'),
            'updated_at' => current_time('mysql'),
            'billing_action_key' => 'agent8v3_onpage_content_rewrite_apply',
            'billing_status' => 'pending_result',
            'billing_cost_cents' => 0,
            'verification_status' => 'pending',
            'restore_available' => true,
            'restored_at' => '',
            'error_code' => '',
            'writes_enabled' => true,
            'billing_enabled' => true,
        );
        $store = get_option('aysa_agent8v3_onpage_content_rewrite_restore_store', array());
        $store = is_array($store) ? $store : array();
        $store[$store_key] = $row;
        update_option('aysa_agent8v3_onpage_content_rewrite_restore_store', array_slice($store, -200, null, true), false);
        return $history_id;
    }

    private function aysa_agent8v3_update_content_rewrite_history_row($history_id, $updates) {
        $history_id = sanitize_text_field((string) $history_id);
        if ($history_id === '') {
            return;
        }
        $store = get_option('aysa_agent8v3_onpage_content_rewrite_restore_store', array());
        $store = is_array($store) ? $store : array();
        foreach ($store as $key => $row) {
            if (!is_array($row) || sanitize_text_field((string) ($row['history_id'] ?? '')) !== $history_id) {
                continue;
            }
            foreach ((array) $updates as $update_key => $value) {
                $safe_key = sanitize_key((string) $update_key);
                if ($safe_key === '') {
                    continue;
                }
                if (in_array($safe_key, array('current_value', 'proposed_value', 'restored_value'), true)) {
                    $row[$safe_key] = wp_kses_post((string) $value);
                } elseif (in_array($safe_key, array('billing_cost_cents', 'project_id', 'entity_id', 'restore_actual_credits_cents'), true)) {
                    $row[$safe_key] = intval($value);
                } else {
                    $row[$safe_key] = sanitize_text_field((string) $value);
                }
            }
            $row['updated_at'] = current_time('mysql');
            $store[$key] = $row;
            update_option('aysa_agent8v3_onpage_content_rewrite_restore_store', array_slice($store, -200, null, true), false);
            return;
        }
    }

    private function aysa_agent8v3_update_content_rewrite_history_billing($item_results, $billing_result) {
        $billing_result = is_array($billing_result) ? $billing_result : array();
        $unit_cost = intval($billing_result['unit_cost_cents'] ?? 5);
        $billing_status = sanitize_key((string) ($billing_result['status'] ?? 'not_billed'));
        foreach ((array) $item_results as $item_result) {
            if (!is_array($item_result) || empty($item_result['history_id'])) {
                continue;
            }
            $cost = sanitize_key((string) ($item_result['status'] ?? '')) === 'verified' && !empty($item_result['changed']) && in_array($billing_status, array('billed', 'idempotent_replay'), true) ? $unit_cost : 0;
            $this->aysa_agent8v3_update_content_rewrite_history_row($item_result['history_id'], array(
                'billing_status' => $cost > 0 ? $billing_status : 'not_billable',
                'billing_cost_cents' => $cost,
            ));
        }
    }

    private function aysa_agent8v3_bill_content_rewrite_apply_result($project_id, $payload, $result) {
        $project_id = intval($project_id);
        $payload = is_array($payload) ? $payload : array();
        $idempotency_key = sanitize_text_field((string) ($payload['idempotency_key'] ?? ''));
        $verified_changed = 0;
        $entity_ids = array();
        foreach ((array) ($result['item_results'] ?? array()) as $item) {
            if (!is_array($item)) {
                continue;
            }
            if ((string) ($item['status'] ?? '') === 'verified' && !empty($item['changed'])) {
                $verified_changed++;
                $entity_ids[] = intval($item['entity_id'] ?? 0);
            }
        }
        $unit_cost_cents = max(1, intval($payload['billing_unit_cents'] ?? 5));
        $cost_cents = $verified_changed * $unit_cost_cents;
        $base = array(
            'status' => $cost_cents > 0 ? 'pending' : 'not_billable_no_verified_changes',
            'unit_cost_cents' => $unit_cost_cents,
            'verified_changed_count' => $verified_changed,
            'cost_cents' => $cost_cents,
            'idempotency_key' => $idempotency_key,
        );
        if ($project_id <= 0 || $idempotency_key === '' || $cost_cents <= 0) {
            return $base;
        }
        $records = get_option('aysa_agent8v3_onpage_content_rewrite_billing_idempotency', array());
        $records = is_array($records) ? $records : array();
        $billing_key = hash('sha256', 'onpage_content_rewrite_apply_billing|' . $project_id . '|' . $idempotency_key);
        if (isset($records[$billing_key]) && is_array($records[$billing_key])) {
            return array_merge($base, $records[$billing_key], array('status' => 'idempotent_replay'));
        }
        $wallet = $this->aysa_agent8v3_image_alt_test_wallet_manager();
        if (!$wallet || !method_exists($wallet, 'track_action_usage')) {
            return array_merge($base, array('status' => 'billing_failed', 'error' => 'wallet_unavailable'));
        }
        $reference = 'agent8v3_onpage_content_rewrite_apply:' . substr($idempotency_key, -24);
        $meta = array(
            'billing_policy' => 'verified_changed_content_targets_only_cost_x5',
            'billing_context' => 'normal_agent8_flow',
            'billing_idempotency_key' => $billing_key,
            'payload_hash' => sanitize_text_field((string) ($payload['payload_hash'] ?? '')),
            'verified_changed_count' => $verified_changed,
            'unit_cost_cents' => $unit_cost_cents,
            'override_cost_cents' => $cost_cents,
            'entity_ids' => array_values(array_filter(array_map('intval', $entity_ids))),
        );
        $ok = $wallet->track_action_usage(
            $project_id,
            'agent8v3_onpage_content_rewrite_apply',
            $reference,
            $meta,
            $cost_cents,
            'action:agent8v3_onpage_content_rewrite_apply'
        );
        if (!$ok) {
            return array_merge($base, array('status' => 'billing_failed', 'error' => 'wallet_debit_failed'));
        }
        $record = array(
            'status' => 'billed',
            'cost_cents' => $cost_cents,
            'unit_cost_cents' => $unit_cost_cents,
            'verified_changed_count' => $verified_changed,
            'reference' => $reference,
            'action_key' => 'agent8v3_onpage_content_rewrite_apply',
            'billed_at' => current_time('mysql'),
        );
        $records[$billing_key] = $record;
        update_option('aysa_agent8v3_onpage_content_rewrite_billing_idempotency', array_slice($records, -100, null, true), false);
        return array_merge($base, $record);
    }

    private function aysa_agent8v3_onpage_content_rewrite_history_readback_foundation($selected_count) {
        return array(
            'status' => 'contract_ready_gate_off',
            'selected_rows' => max(0, intval($selected_count)),
            'history_snapshot_required' => true,
            'history_persisted' => false,
            'history_store_available' => true,
            'history_store_option' => 'aysa_agent8v3_onpage_content_rewrite_restore_store',
            'readback_required' => true,
            'readback_executed' => false,
            'restore_required' => true,
            'restore_available' => false,
            'restore_store_available' => true,
            'hash_conflict_guard_required' => true,
            'billing_candidate_ready' => false,
            'billing_enabled' => false,
            'verified_only_billing_model' => 'verified_changed_content_targets_only_cost_x5_gate_off',
        );
    }

    private function aysa_agent8v3_onpage_content_rewrite_history_readback_contract($item, $target, $before_hash, $after_hash, $surface) {
        $item = is_array($item) ? $item : array();
        $item_id = sanitize_text_field((string) ($item['item_id'] ?? $item['entity_key'] ?? ''));
        $entity_key = sanitize_text_field((string) ($item['entity_key'] ?? $item_id));
        $current_value = trim(wp_strip_all_tags((string) ($item['current_value'] ?? $item['before_value'] ?? '')));
        $proposed_value = trim(wp_strip_all_tags((string) ($item['proposed_value'] ?? $item['after_value'] ?? '')));
        $current_hash = hash('sha256', preg_replace('/\s+/u', ' ', $current_value) ?: '');
        $proposed_hash = $proposed_value !== '' ? hash('sha256', preg_replace('/\s+/u', ' ', $proposed_value)) : '';
        return array(
            'history_snapshot' => array(
                'status' => 'preview_only_not_persisted',
                'item_id' => $item_id,
                'entity_key' => $entity_key,
                'target' => $target,
                'content_surface' => $surface,
                'before_hash' => $before_hash,
                'after_hash' => $after_hash,
                'current_value_hash' => $current_hash,
                'proposed_value_hash' => $proposed_hash,
                'requires_before_value' => true,
                'requires_after_value' => true,
                'requires_actor' => true,
                'requires_idempotency_key' => true,
                'persisted' => false,
            ),
            'readback_contract' => array(
                'status' => 'required_not_executed_gate_off',
                'target' => $target,
                'expected_after_hash' => $after_hash,
                'verify_changed' => true,
                'verify_target_value_hash' => true,
                'executed' => false,
            ),
            'restore_contract' => array(
                'status' => 'required_not_available_until_verified_apply',
                'restore_allowed' => false,
                'non_billable' => true,
                'requires_current_hash_match' => true,
                'expected_current_hash_before_restore' => $after_hash,
                'restore_to_hash' => $before_hash,
            ),
            'billing_candidate' => array(
                'status' => 'not_billable_gate_off',
                'billable' => false,
                'bill_only_if_verified' => true,
                'bill_only_if_changed' => true,
                'unit_cents' => 5,
            ),
        );
    }

    private function aysa_agent8v3_apply_onpage_execution_plan($plan, $request_payload) {
        $plan = is_array($plan) ? $plan : array();
        $idempotency_key = sanitize_text_field((string) ($plan['idempotency_key'] ?? ''));
        $payload_hash = sanitize_text_field((string) ($plan['payload_hash'] ?? ''));
        $section = sanitize_key((string) ($plan['section'] ?? ''));
        $operation = sanitize_key((string) ($plan['operation'] ?? ''));
        $items = is_array($plan['payload']['accepted_items'] ?? null) ? $plan['payload']['accepted_items'] : array();
        $source_run_id = $this->aysa_agent8v3_resolve_onpage_execution_source_run_id_from_plan($plan, $request_payload, $items);
        $selected_entity_keys = array_values(array_unique(array_filter(array_map(static function ($item): string {
            return is_array($item) ? sanitize_text_field((string) ($item['entity_key'] ?? '')) : '';
        }, $items))));
        $selected_provider_output_ids = array_values(array_unique(array_filter(array_map(static function ($item): string {
            return is_array($item) ? sanitize_text_field((string) ($item['provider_output_id'] ?? '')) : '';
        }, $items))));
        $execution_ref = $idempotency_key !== '' ? 'onpage_execution:' . hash('sha256', $idempotency_key) : '';
        $result = array(
            'action' => 'execute_onpage_plan',
            'section' => $section,
            'operation' => $operation,
            'run_id' => intval($plan['run_id'] ?? $request_payload['run_id'] ?? $source_run_id),
            'idempotency_key' => $idempotency_key,
            'source_run_id' => $source_run_id,
            'execution_id' => $execution_ref,
            'execution_ref' => $execution_ref,
            'action_reference' => $execution_ref,
            'action_ref' => $execution_ref,
            'confirmation_kind' => sanitize_key((string) ($plan['confirmation_kind'] ?? '')),
            'user_confirmed' => !empty($plan['user_confirmed']) || !empty($plan['confirmed_by_user']),
            'selected_entity_keys' => $selected_entity_keys,
            'selected_provider_output_ids' => $selected_provider_output_ids,
            'payload_hash' => $payload_hash,
            'selection_hash' => '',
            'analysis_bundle_id' => sanitize_text_field((string) ($plan['analysis_bundle_id'] ?? $request_payload['analysis_bundle_id'] ?? '')),
            'proposal_hashes' => array(),
            'old_value_snapshot' => array(),
            'proposed_value_snapshot' => array(),
            'max_total_cost_cents' => intval($plan['max_total_cost_cents'] ?? $request_payload['max_total_cost_cents'] ?? 0),
            'source_metadata_run_id' => intval($plan['source_metadata_run_id'] ?? $request_payload['source_metadata_run_id'] ?? $source_run_id),
            'durable_identity_status' => 'not_started',
            'pre_execution_snapshot_persisted' => false,
            'post_execution_snapshot_persisted' => false,
            'terminal_snapshot_persisted' => false,
            'notification_action_reference' => '',
            'notification_status' => 'not_sent',
            'ai_visibility_intervention_callback' => class_exists('Aysa_AI_Visibility_Intervention_Correlation')
                ? Aysa_AI_Visibility_Intervention_Correlation::normalize($plan['ai_visibility_intervention_callback'] ?? array())
                : array(),
            'accepted_count' => count($items),
            'applied_count' => 0,
            'failed_count' => 0,
            'skipped_count' => 0,
            'verified_count' => 0,
            'status' => 'skipped',
            'errors' => array(),
            'item_results' => array(),
            'billing_action_key' => 'onpage_plan_execution_bridge',
            'billing_policy' => 'verified_changed_onpage_fields_only_cost_x5',
            'billing_unit_cents' => 5,
            'billing_cost_cents' => 0,
            'actual_credits_cents' => 0,
            'billed_units' => 0,
            'charged_credits' => 0,
            'billing_ledger_reference' => '',
            'option_keys' => array('aysa_agent8v3_onpage_execution_idempotency', 'aysa_agent8v3_onpage_executions'),
        );
        if ($section === 'content' && $operation === 'apply_rewrite') {
            $plan['wp_project_id'] = intval($request_payload['wp_project_id'] ?? get_option('aysa_project_id', 0));
            $plan['project_id'] = intval($request_payload['project_id'] ?? 0);
            $content_rewrite_result = !empty($plan['dry_run'])
                ? $this->aysa_agent8v3_onpage_content_rewrite_apply_dry_run_bridge($plan)
                : $this->aysa_agent8v3_onpage_content_rewrite_apply_controlled_bridge($plan);
            $result = array_merge($result, $content_rewrite_result);
            $result['section'] = 'content';
            $result['operation'] = 'apply_rewrite';
            $result['accepted_count'] = count($items);
            $result['skipped_count'] = intval($content_rewrite_result['skipped_count'] ?? $result['skipped_count'] ?? 0);
            $result['applied_count'] = intval($content_rewrite_result['applied_count'] ?? $result['applied_count'] ?? 0);
            $result['verified_count'] = intval($content_rewrite_result['verified_count'] ?? $result['verified_count'] ?? 0);
            $result['failed_count'] = intval($content_rewrite_result['failed_count'] ?? $result['failed_count'] ?? 0);
            $result['actual_credits_cents'] = intval($content_rewrite_result['actual_credits_cents'] ?? $result['actual_credits_cents'] ?? 0);
            $this->aysa_agent8v3_log_onpage_execution($plan, $result);
            return $result;
        }
        $valid_operation = ($section === 'meta_snippets' && $operation === 'apply_meta')
            || ($section === 'aeo_content_qa' && $operation === 'apply_rewrite');
        if (
            $section === 'meta_snippets'
            && $operation === 'apply_meta'
            && (
                sanitize_key((string) ($plan['confirmation_kind'] ?? '')) !== 'onpage_seo_apply_accepted_bundle'
                || empty($plan['onpage_seo_apply_accepted_bundle'])
                || (empty($plan['user_confirmed']) && empty($plan['confirmed_by_user']))
            )
        ) {
            $result['status'] = 'rejected';
            $result['error'] = 'onpage_apply_requires_confirmation';
            $this->aysa_agent8v3_log_onpage_execution($plan, $result);
            return $result;
        }
        if ($idempotency_key === '' || !$valid_operation || empty($items)) {
            $result['status'] = 'rejected';
            $result['error'] = 'onpage_execution_plan_invalid';
            $this->aysa_agent8v3_log_onpage_execution($plan, $result);
            return $result;
        }

        $project_id = intval($request_payload['wp_project_id'] ?? get_option('aysa_project_id', 0));
        if ($project_id <= 0) {
            $result['status'] = 'failed';
            $result['error'] = 'project_id_required';
            $this->aysa_agent8v3_log_onpage_execution($plan, $result);
            return $result;
        }

        $applied = get_option('aysa_agent8v3_onpage_execution_idempotency', array());
        $applied = is_array($applied) ? $applied : array();
        $previous_item_results = array();
        if (!empty($applied[$idempotency_key])) {
            $previous_result = is_array($applied[$idempotency_key]) ? $applied[$idempotency_key] : array();
            $previous_item_results = is_array($previous_result['item_results'] ?? null) ? $previous_result['item_results'] : array();
            $previous_failed = intval($previous_result['failed_count'] ?? 0);
            $previous_skipped = intval($previous_result['skipped_count'] ?? 0);
            if ($previous_failed <= 0 && intval($previous_result['applied_count'] ?? 0) > 0) {
                if (($previous_result['durable_identity_status'] ?? '') !== 'complete'
                    || empty($previous_result['pre_execution_snapshot_persisted'])
                    || empty($previous_result['post_execution_snapshot_persisted'])) {
                    $result['status'] = 'rejected';
                    $result['completion_state'] = 'completed_incomplete_evidence';
                    $result['durable_identity_status'] = 'incomplete';
                    $result['error'] = 'onpage_durable_identity_missing';
                    $result['contract_error'] = 'Existing replay has no complete durable identity; it remains quarantined.';
                    $this->aysa_agent8v3_log_onpage_execution($plan, $result);
                    return $result;
                }
                $result = array_merge($result, $previous_result);
                $result['status'] = 'already_synced';
                $this->aysa_agent8v3_log_onpage_execution($plan, $result);
                return $result;
            }
        }

        $identity = $this->aysa_agent8v3_prepare_onpage_durable_apply_identity(
            $plan,
            $request_payload,
            $items,
            $project_id,
            $execution_ref
        );
        if (empty($identity['ok'])) {
            $result['status'] = 'rejected';
            $result['completion_state'] = 'completed_incomplete_evidence';
            $result['durable_identity_status'] = 'incomplete';
            $result['error'] = 'onpage_durable_identity_contract_required';
            $result['contract_errors'] = array_values(array_slice(is_array($identity['errors'] ?? null) ? $identity['errors'] : array(), 0, 20));
            $this->aysa_agent8v3_log_onpage_execution($plan, $result);
            return $result;
        }
        $preSnapshot = is_array($identity['snapshot'] ?? null) ? $identity['snapshot'] : array();
        if (!$this->aysa_agent8v3_persist_onpage_pre_execution_snapshot($preSnapshot)) {
            $result['status'] = 'rejected';
            $result['completion_state'] = 'completed_incomplete_evidence';
            $result['durable_identity_status'] = 'incomplete';
            $result['error'] = 'onpage_pre_execution_snapshot_persist_failed';
            $result['contract_errors'] = array('Durable pre-execution evidence could not be verified before the first write.');
            $this->aysa_agent8v3_log_onpage_execution($plan, $result);
            return $result;
        }
        $result['selection_hash'] = (string) ($preSnapshot['selection_hash'] ?? '');
        $result['idempotency_key'] = (string) ($preSnapshot['idempotency_key'] ?? $idempotency_key);
        $result['run_id'] = intval($preSnapshot['run_id'] ?? $result['run_id'] ?? $source_run_id);
        $result['analysis_bundle_id'] = (string) ($preSnapshot['analysis_bundle_id'] ?? '');
        $result['source_metadata_run_id'] = intval($preSnapshot['source_metadata_run_id'] ?? $source_run_id);
        $result['proposal_hashes'] = is_array($preSnapshot['proposal_hashes'] ?? null) ? $preSnapshot['proposal_hashes'] : array();
        $result['old_value_snapshot'] = is_array($preSnapshot['old_value_snapshot'] ?? null) ? $preSnapshot['old_value_snapshot'] : array();
        $result['proposed_value_snapshot'] = is_array($preSnapshot['proposed_value_snapshot'] ?? null) ? $preSnapshot['proposed_value_snapshot'] : array();
        $result['max_total_cost_cents'] = intval($preSnapshot['max_total_cost_cents'] ?? 0);
        $result['ai_visibility_intervention_callback'] = is_array($preSnapshot['ai_visibility_intervention_callback'] ?? null)
            ? $preSnapshot['ai_visibility_intervention_callback']
            : array();
        $result['durable_identity_status'] = 'complete';
        $result['pre_execution_snapshot_persisted'] = true;
        $result['row_context'] = is_array($preSnapshot['row_context'] ?? null) ? $preSnapshot['row_context'] : array();

        $previous_applied = array();
        foreach ($previous_item_results as $previous_item) {
            if (!is_array($previous_item) || (string) ($previous_item['status'] ?? '') !== 'applied') {
                continue;
            }
            $previous_item_id = sanitize_text_field((string) ($previous_item['item_id'] ?? ''));
            if ($previous_item_id !== '') {
                $previous_applied[$previous_item_id] = true;
            }
        }

        foreach ($items as $item) {
            if (!is_array($item)) {
                $result['skipped_count']++;
                $result['item_results'][] = array(
                    'provider_output_id' => '',
                    'proposal_hash' => '',
                    'status' => 'skipped',
                    'reason' => 'invalid_item',
                );
                continue;
            }
            $entity_key = sanitize_text_field((string) ($item['entity_key'] ?? ''));
            $item_id = sanitize_text_field((string) ($item['item_id'] ?? $entity_key));
            if ($entity_key === '') {
                $result['skipped_count']++;
                $result['item_results'][] = array(
                    'item_id' => $item_id,
                    'entity_key' => '',
                    'provider_output_id' => sanitize_text_field((string) ($item['provider_output_id'] ?? '')),
                    'proposal_hash' => sanitize_text_field((string) ($item['proposal_hash'] ?? '')),
                    'status' => 'skipped',
                    'reason' => 'entity_key_required',
                );
                continue;
            }
            if ($item_id !== '' && isset($previous_applied[$item_id])) {
                $result['skipped_count']++;
                $result['item_results'][] = array(
                    'item_id' => $item_id,
                    'entity_key' => $entity_key,
                    'provider_output_id' => sanitize_text_field((string) ($item['provider_output_id'] ?? '')),
                    'proposal_hash' => sanitize_text_field((string) ($item['proposal_hash'] ?? '')),
                    'status' => 'already_synced',
                    'reason' => '',
                );
                continue;
            }
            $service = $this->aysa_onpage_workspace_service();
            if (!$service) {
                $apply = array('ok' => false, 'error' => 'onpage_workspace_service_unavailable');
            } elseif ($operation === 'apply_meta') {
                $apply = $service->apply_meta_output($project_id, $entity_key);
            } else {
                $apply = $this->aysa_agent8_apply_onpage_entity_operation($project_id, array(
                    'entity_key' => $entity_key,
                ), $operation);
                $apply = array(
                    'ok' => !empty($apply['applied']),
                    'error' => sanitize_key((string) ($apply['reason'] ?? 'onpage_apply_failed')),
                );
            }
            if (!empty($apply['ok'])) {
                if ($operation === 'apply_meta') {
                    foreach ($this->aysa_agent8v3_onpage_meta_verified_item_results($item_id, $entity_key, $apply, $item) as $field_result) {
                        $status = sanitize_key((string) ($field_result['status'] ?? ''));
                        if ($status === 'verified' && !empty($field_result['changed'])) {
                            $result['applied_count']++;
                            $result['verified_count']++;
                        } else {
                            $result['skipped_count']++;
                        }
                        $result['item_results'][] = $field_result;
                    }
                } else {
                    $result['applied_count']++;
                    $result['item_results'][] = array(
                        'item_id' => $item_id,
                        'entity_key' => $entity_key,
                        'provider_output_id' => sanitize_text_field((string) ($item['provider_output_id'] ?? '')),
                        'proposal_hash' => sanitize_text_field((string) ($item['proposal_hash'] ?? '')),
                        'status' => 'applied',
                        'reason' => '',
                    );
                }
            } else {
                $reason = sanitize_key((string) ($apply['error'] ?? $apply['reason'] ?? 'onpage_apply_failed'));
                $result['failed_count']++;
                $result['errors'][] = $reason;
                $result['item_results'][] = array(
                    'item_id' => $item_id,
                    'entity_key' => $entity_key,
                    'provider_output_id' => sanitize_text_field((string) ($item['provider_output_id'] ?? '')),
                    'proposal_hash' => sanitize_text_field((string) ($item['proposal_hash'] ?? '')),
                    'status' => 'failed',
                    'reason' => $reason,
                );
            }
        }

        if ($operation === 'apply_meta') {
            if ($result['failed_count'] > 0 && $result['verified_count'] === 0) {
                $result['status'] = 'failed';
            } elseif ($result['verified_count'] > 0 && $result['failed_count'] === 0) {
                $result['status'] = 'verified';
            } elseif ($result['verified_count'] > 0) {
                $result['status'] = 'partial_failed';
            } else {
                $result['status'] = 'skipped';
            }
            $result['billing_cost_cents'] = intval($result['verified_count']) * intval($result['billing_unit_cents']);
            $result['actual_credits_cents'] = intval($result['billing_cost_cents']);
            // Keep the legacy status for billing/idempotency, but expose a
            // separate client verdict that requires terminal evidence for
            // every selected entity before it can look complete.
            $result['completion_state'] = $this->aysa_agent8v3_onpage_completion_state($result, $selected_entity_keys);
            $result['item_results'] = $this->aysa_agent8v3_enrich_onpage_durable_item_results(
                is_array($result['item_results'] ?? null) ? $result['item_results'] : array(),
                $result['row_context']
            );
        } else {
            $result['status'] = $result['failed_count'] > 0 && $result['applied_count'] === 0 ? 'failed' : 'completed';
            $result['completion_state'] = $result['status'];
        }
        unset($result['row_context']);
        $result['post_execution_snapshot_persisted'] = false;
        $result['terminal_snapshot_persisted'] = false;
        $applied[$idempotency_key] = array(
            'section' => $section,
            'operation' => $operation,
            'source_run_id' => $source_run_id,
            'execution_ref' => $execution_ref,
            'action_ref' => $execution_ref,
            'execution_id' => $execution_ref,
            'action_reference' => $execution_ref,
            'confirmation_kind' => sanitize_key((string) ($result['confirmation_kind'] ?? '')),
            'user_confirmed' => !empty($result['user_confirmed']),
            'selected_entity_keys' => $selected_entity_keys,
            'selected_provider_output_ids' => $selected_provider_output_ids,
            'selection_hash' => (string) ($result['selection_hash'] ?? ''),
            'analysis_bundle_id' => (string) ($result['analysis_bundle_id'] ?? ''),
            'source_metadata_run_id' => intval($result['source_metadata_run_id'] ?? $source_run_id),
            'idempotency_key' => sanitize_text_field((string) ($result['idempotency_key'] ?? $idempotency_key)),
            'run_id' => intval($result['run_id'] ?? $source_run_id),
            'proposal_hashes' => is_array($result['proposal_hashes'] ?? null) ? $result['proposal_hashes'] : array(),
            'old_value_snapshot' => is_array($result['old_value_snapshot'] ?? null) ? $result['old_value_snapshot'] : array(),
            'proposed_value_snapshot' => is_array($result['proposed_value_snapshot'] ?? null) ? $result['proposed_value_snapshot'] : array(),
            'max_total_cost_cents' => intval($result['max_total_cost_cents'] ?? 0),
            'durable_identity_status' => 'complete',
            'pre_execution_snapshot_persisted' => true,
            'post_execution_snapshot_persisted' => false,
            'terminal_snapshot_persisted' => false,
            'payload_hash' => $payload_hash,
            'accepted_count' => count($items),
            'applied_count' => intval($result['applied_count'] ?? 0),
            'verified_count' => intval($result['verified_count'] ?? 0),
            'failed_count' => intval($result['failed_count'] ?? 0),
            'skipped_count' => intval($result['skipped_count'] ?? 0),
            'completion_state' => sanitize_key((string) ($result['completion_state'] ?? $result['status'] ?? 'unknown')),
            'billing_action_key' => sanitize_key((string) ($result['billing_action_key'] ?? 'onpage_plan_execution_bridge')),
            'billing_policy' => sanitize_text_field((string) ($result['billing_policy'] ?? 'verified_changed_onpage_fields_only_cost_x5')),
            'billing_unit_cents' => intval($result['billing_unit_cents'] ?? 5),
            'billing_cost_cents' => intval($result['billing_cost_cents'] ?? 0),
            'actual_credits_cents' => intval($result['actual_credits_cents'] ?? 0),
            'billed_units' => intval($result['billed_units'] ?? 0),
            'charged_credits' => intval($result['charged_credits'] ?? 0),
            'billing_ledger_reference' => sanitize_text_field((string) ($result['billing_ledger_reference'] ?? '')),
            'ai_visibility_intervention_callback' => is_array($result['ai_visibility_intervention_callback'] ?? null)
                ? $result['ai_visibility_intervention_callback']
                : array(),
            'status' => sanitize_key((string) ($result['status'] ?? 'unknown')),
            'errors' => array_values(array_slice(is_array($result['errors'] ?? null) ? $result['errors'] : array(), 0, 20)),
            'item_results' => array_slice($result['item_results'], 0, 100),
            'option_keys' => $result['option_keys'],
            'timestamp' => current_time('mysql'),
        );
        update_option('aysa_agent8v3_onpage_execution_idempotency', array_slice($applied, -100, null, true), false);
        $this->aysa_agent8v3_log_onpage_execution($plan, $result);
        $terminalPersisted = $this->aysa_agent8v3_mark_onpage_terminal_snapshot_persisted($execution_ref, $idempotency_key);
        $result['post_execution_snapshot_persisted'] = $terminalPersisted;
        $result['terminal_snapshot_persisted'] = $terminalPersisted;
        return $result;
    }

    private function aysa_agent8v3_prepare_onpage_durable_apply_identity($plan, $request_payload, $items, $project_id, $execution_ref) {
        $plan = is_array($plan) ? $plan : array();
        $request_payload = is_array($request_payload) ? $request_payload : array();
        $items = is_array($items) ? array_values(array_filter($items, 'is_array')) : array();
        $errors = array();
        $idempotencyKey = sanitize_text_field((string) ($plan['idempotency_key'] ?? ''));
        $confirmationKind = sanitize_key((string) ($plan['confirmation_kind'] ?? ''));
        $userConfirmed = !empty($plan['user_confirmed']) || !empty($plan['confirmed_by_user']);
        $sourceRunId = intval($plan['source_run_id'] ?? $plan['run_id'] ?? $request_payload['source_run_id'] ?? $request_payload['run_id'] ?? 0);
        $sourceMetadataRunId = intval($plan['source_metadata_run_id'] ?? $request_payload['source_metadata_run_id'] ?? $sourceRunId);
        $analysisBundleId = sanitize_text_field((string) ($plan['analysis_bundle_id'] ?? $request_payload['analysis_bundle_id'] ?? ''));
        $maxCostPresent = array_key_exists('max_total_cost_cents', $plan)
            || array_key_exists('max_total_cost', $plan)
            || array_key_exists('max_cost_cents', $plan)
            || array_key_exists('max_total_cost_cents', $request_payload)
            || array_key_exists('max_total_cost', $request_payload)
            || array_key_exists('max_cost_cents', $request_payload);
        $maxCost = intval($plan['max_total_cost_cents'] ?? $plan['max_total_cost'] ?? $plan['max_cost_cents'] ?? $request_payload['max_total_cost_cents'] ?? $request_payload['max_total_cost'] ?? $request_payload['max_cost_cents'] ?? 0);
        $approvedSelectionCount = intval($plan['approved_selection_count'] ?? 0);
        $approvedSelectionHash = sanitize_text_field((string) ($plan['approved_selection_hash'] ?? ''));
        $approvedMaxCost = intval($plan['approved_max_total_cost_cents'] ?? 0);
        $approvedEntityKeys = array_values(array_unique(array_filter(array_map('sanitize_text_field', is_array($plan['approved_selected_entity_keys'] ?? null) ? $plan['approved_selected_entity_keys'] : array()))));
        $approvedProviderOutputIds = array_values(array_unique(array_filter(array_map('sanitize_text_field', is_array($plan['approved_selected_provider_output_ids'] ?? null) ? $plan['approved_selected_provider_output_ids'] : array()))));
        if ($idempotencyKey === '') $errors[] = 'idempotency_key_required';
        if ($execution_ref === '') $errors[] = 'execution_id_required';
        if ($confirmationKind !== 'onpage_seo_apply_accepted_bundle') $errors[] = 'confirmation_kind_required';
        if (!$userConfirmed) $errors[] = 'user_confirmation_required';
        if ($sourceRunId <= 0) $errors[] = 'source_run_id_required';
        if (!$maxCostPresent || $maxCost < 0) $errors[] = 'max_cost_required';
        if ($project_id <= 0) $errors[] = 'project_id_required';
        if ($items === array()) $errors[] = 'selected_items_required';

        $selectedEntityKeys = array();
        $selectedProviderOutputIds = array();
        $proposalHashes = array();
        $approvedHashItems = array();
        $rowContext = array();
        $service = $this->aysa_onpage_workspace_service();
        foreach ($items as $item) {
            $entityKey = sanitize_text_field((string) ($item['entity_key'] ?? ''));
            $providerOutputId = sanitize_text_field((string) ($item['provider_output_id'] ?? ''));
            $proposalHash = sanitize_text_field((string) ($item['proposal_hash'] ?? ''));
            if ($entityKey === '') { $errors[] = 'entity_key_required'; continue; }
            if ($providerOutputId === '') $errors[] = 'provider_output_id_required:' . $entityKey;
            if ($proposalHash === '') $errors[] = 'proposal_hash_required:' . $entityKey;
            $selectedEntityKeys[] = $entityKey;
            if ($providerOutputId !== '') $selectedProviderOutputIds[] = $providerOutputId;
            if ($proposalHash !== '') $proposalHashes[$entityKey] = $proposalHash;
            $approvedHashItems[] = array(
                'item_id' => $entityKey,
                'entity_key' => $entityKey,
                'provider_output_id' => $providerOutputId,
                'proposal_hash' => $proposalHash,
            );

            $detail = $service && method_exists($service, 'get_detail') ? $service->get_detail($project_id, $entityKey, true) : array();
            $inventory = is_array($detail['inventory'] ?? null) ? $detail['inventory'] : array();
            $metadataReadback = is_array($detail['metadata_readback'] ?? null) ? $detail['metadata_readback'] : array();
            $liveProvider = is_array($metadataReadback['live_provider'] ?? null) ? $metadataReadback['live_provider'] : array();
            $provider = sanitize_text_field((string) ($inventory['metadata_provider'] ?? $inventory['metadata_provider_mode'] ?? $detail['metadata_provider'] ?? $metadataReadback['provider'] ?? $metadataReadback['provider_mode'] ?? ''));
            $routeEvidence = !empty($inventory['metadata_route_evidence_available']) || $provider !== '';
            $hasOldTitle = array_key_exists('current_meta_title', $inventory) || array_key_exists('current_meta_title', $detail) || array_key_exists('meta_title', $liveProvider);
            $hasOldDescription = array_key_exists('current_meta_description', $inventory) || array_key_exists('current_meta_description', $detail) || array_key_exists('meta_description', $liveProvider);
            if (!$routeEvidence) $errors[] = 'metadata_route_evidence_required:' . $entityKey;
            if (!$hasOldTitle || !$hasOldDescription) $errors[] = 'old_metadata_snapshot_required:' . $entityKey;
            $metaPackage = is_array($detail['meta_package'] ?? null) ? $detail['meta_package'] : array();
            $oldTitle = trim((string) ($item['old_meta_title'] ?? $inventory['current_meta_title'] ?? $detail['current_meta_title'] ?? $liveProvider['meta_title'] ?? ''));
            $oldDescription = trim((string) ($item['old_meta_description'] ?? $inventory['current_meta_description'] ?? $detail['current_meta_description'] ?? $liveProvider['meta_description'] ?? ''));
            $proposedTitle = trim((string) ($item['proposed_meta_title'] ?? $item['meta_title'] ?? $metaPackage['meta_title'] ?? $metaPackage['title'] ?? ''));
            $proposedDescription = trim((string) ($item['proposed_meta_description'] ?? $item['meta_description'] ?? $metaPackage['meta_description'] ?? $metaPackage['description'] ?? ''));
            if ($proposedTitle === '' && $proposedDescription === '') $errors[] = 'proposed_metadata_snapshot_required:' . $entityKey;
            $rowContext[$entityKey] = array(
                'entity_key' => $entityKey,
                'url' => esc_url_raw((string) ($inventory['url'] ?? $detail['url'] ?? $item['url'] ?? '')),
                'provider_output_id' => $providerOutputId,
                'proposal_hash' => $proposalHash,
                'provider' => $provider,
                'old_meta_title' => $oldTitle,
                'old_meta_description' => $oldDescription,
                'proposed_meta_title' => $proposedTitle,
                'proposed_meta_description' => $proposedDescription,
                'history_id' => '',
                'restore_available' => false,
            );
        }
        $selectedEntityKeys = array_values(array_unique($selectedEntityKeys));
        $selectedProviderOutputIds = array_values(array_unique($selectedProviderOutputIds));
        sort($selectedEntityKeys, SORT_STRING);
        sort($selectedProviderOutputIds, SORT_STRING);
        sort($approvedEntityKeys, SORT_STRING);
        sort($approvedProviderOutputIds, SORT_STRING);
        usort($approvedHashItems, static function($left, $right) {
            return strcmp((string) ($left['entity_key'] ?? ''), (string) ($right['entity_key'] ?? ''));
        });
        $resolvedApprovedSelectionHash = hash('sha256', wp_json_encode($approvedHashItems));
        $selectionHash = hash('sha256', wp_json_encode(array(
            'selected_entity_keys' => $selectedEntityKeys,
            'selected_provider_output_ids' => $selectedProviderOutputIds,
        ), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
        if (count($selectedEntityKeys) !== count($items)) $errors[] = 'selected_entity_keys_incomplete';
        if (count($selectedProviderOutputIds) !== count($items)) $errors[] = 'selected_provider_output_ids_incomplete';
        if ($approvedSelectionCount <= 0 || $approvedSelectionCount !== count($items)) $errors[] = 'approved_selection_count_mismatch';
        if ($approvedEntityKeys !== $selectedEntityKeys) $errors[] = 'approved_entity_keys_mismatch';
        if ($approvedProviderOutputIds !== $selectedProviderOutputIds) $errors[] = 'approved_provider_output_ids_mismatch';
        if ($approvedSelectionHash === '' || !hash_equals($approvedSelectionHash, $resolvedApprovedSelectionHash) || !hash_equals($approvedSelectionHash, sanitize_text_field((string) ($plan['payload_hash'] ?? '')))) $errors[] = 'approved_selection_hash_mismatch';
        if ($approvedMaxCost <= 0 || $approvedMaxCost !== count($items) * 2 * 5 || $maxCost !== $approvedMaxCost) $errors[] = 'approved_cost_cap_mismatch';
        $errors = array_values(array_unique($errors));
        if ($errors !== array()) return array('ok' => false, 'errors' => $errors);

        return array('ok' => true, 'snapshot' => array(
            'contract_version' => 'onpage_durable_apply_identity_v1',
            'phase' => 'pre_execution',
            'status' => 'pre_execution_snapshot_persisted',
            'execution_id' => $execution_ref,
            'execution_ref' => $execution_ref,
            'action_reference' => $execution_ref,
            'action_ref' => $execution_ref,
            'idempotency_key' => $idempotencyKey,
            'confirmation_kind' => $confirmationKind,
            'user_confirmed' => $userConfirmed,
            'source_run_id' => $sourceRunId,
            'source_metadata_run_id' => $sourceMetadataRunId,
            'analysis_bundle_id' => $analysisBundleId,
            'selection_hash' => $selectionHash,
            'selected_entity_keys' => $selectedEntityKeys,
            'selected_provider_output_ids' => $selectedProviderOutputIds,
            'proposal_hashes' => $proposalHashes,
            'old_value_snapshot' => array_map(static fn($row) => array('entity_key' => $row['entity_key'], 'url' => $row['url'], 'meta_title' => $row['old_meta_title'], 'meta_description' => $row['old_meta_description']), $rowContext),
            'proposed_value_snapshot' => array_map(static fn($row) => array('entity_key' => $row['entity_key'], 'url' => $row['url'], 'meta_title' => $row['proposed_meta_title'], 'meta_description' => $row['proposed_meta_description']), $rowContext),
            'row_context' => $rowContext,
            'max_total_cost_cents' => $maxCost,
            'approved_selection_count' => $approvedSelectionCount,
            'approved_selected_entity_keys' => $approvedEntityKeys,
            'approved_selected_provider_output_ids' => $approvedProviderOutputIds,
            'approved_selection_hash' => $approvedSelectionHash,
            'approved_max_total_cost_cents' => $approvedMaxCost,
            'ai_visibility_intervention_callback' => class_exists('Aysa_AI_Visibility_Intervention_Correlation')
                ? Aysa_AI_Visibility_Intervention_Correlation::normalize($plan['ai_visibility_intervention_callback'] ?? array())
                : array(),
            'created_at' => current_time('mysql'),
        ));
    }

    private function aysa_agent8v3_persist_onpage_pre_execution_snapshot($snapshot) {
        $snapshot = is_array($snapshot) ? $snapshot : array();
        $events = get_option('aysa_agent8v3_onpage_executions', array());
        $events = is_array($events) ? $events : array();
        array_unshift($events, $snapshot);
        update_option('aysa_agent8v3_onpage_executions', array_slice($events, 0, 100), false);
        foreach ((array) get_option('aysa_agent8v3_onpage_executions', array()) as $event) {
            if (is_array($event) && ($event['phase'] ?? '') === 'pre_execution' && ($event['execution_ref'] ?? '') === ($snapshot['execution_ref'] ?? '')) return true;
        }
        return false;
    }

    private function aysa_agent8v3_enrich_onpage_durable_item_results($items, $rowContext) {
        $rowContext = is_array($rowContext) ? $rowContext : array();
        return array_values(array_map(static function ($item) use ($rowContext) {
            if (!is_array($item)) return $item;
            $entityKey = sanitize_text_field((string) ($item['entity_key'] ?? ''));
            $context = is_array($rowContext[$entityKey] ?? null) ? $rowContext[$entityKey] : array();
            $status = sanitize_key((string) ($item['status'] ?? ''));
            $changed = !empty($item['changed']) && in_array($status, array('verified', 'verified_changed'), true);
            $item['url'] = esc_url_raw((string) ($context['url'] ?? $item['url'] ?? ''));
            $item['provider_output_id'] = sanitize_text_field((string) ($item['provider_output_id'] ?? $context['provider_output_id'] ?? ''));
            $item['proposal_hash'] = sanitize_text_field((string) ($item['proposal_hash'] ?? $context['proposal_hash'] ?? ''));
            $item['row_verdict'] = $status === 'verified' && $changed ? 'applied_verified' : (in_array($status, array('skipped_no_change', 'already_synced', 'no_change'), true) ? 'no_change' : ($status !== '' ? $status : 'manual_review'));
            $item['restore_available'] = trim((string) ($item['history_id'] ?? '')) !== '';
            $item['billing_units'] = $changed ? 5 : 0;
            $item['billing_reference'] = '';
            $item['error_reason'] = sanitize_text_field((string) ($item['reason'] ?? ''));
            return $item;
        }, is_array($items) ? $items : array()));
    }

    private function aysa_agent8v3_mark_onpage_terminal_snapshot_persisted($execution_ref, $idempotency_key = '') {
        $execution_ref = sanitize_text_field((string) $execution_ref);
        $idempotency_key = sanitize_text_field((string) $idempotency_key);
        if ($execution_ref === '') return false;
        $events = get_option('aysa_agent8v3_onpage_executions', array());
        $events = is_array($events) ? $events : array();
        $found = false;
        foreach ($events as $index => $event) {
            if (!is_array($event) || ($event['phase'] ?? '') !== 'post_execution' || ($event['execution_ref'] ?? '') !== $execution_ref) continue;
            $events[$index]['post_execution_snapshot_persisted'] = true;
            $events[$index]['terminal_snapshot_persisted'] = true;
            $found = true;
            break;
        }
        if (!$found) return false;
        update_option('aysa_agent8v3_onpage_executions', $events, false);
        if ($idempotency_key !== '') {
            $applied = get_option('aysa_agent8v3_onpage_execution_idempotency', array());
            $applied = is_array($applied) ? $applied : array();
            if (isset($applied[$idempotency_key]) && is_array($applied[$idempotency_key])) {
                $applied[$idempotency_key]['post_execution_snapshot_persisted'] = true;
                $applied[$idempotency_key]['terminal_snapshot_persisted'] = true;
                $applied[$idempotency_key]['durable_identity_status'] = 'complete';
                $applied[$idempotency_key]['timestamp'] = current_time('mysql');
                update_option('aysa_agent8v3_onpage_execution_idempotency', array_slice($applied, -100, null, true), false);
                return true;
            }
            return false;
        }
        return true;
    }

    private function aysa_agent8v3_onpage_completion_state($result, $selected_entity_keys) {
        $result = is_array($result) ? $result : array();
        $selected_entity_keys = array_values(array_unique(array_filter(array_map('sanitize_text_field', is_array($selected_entity_keys) ? $selected_entity_keys : array()))));
        $item_results = is_array($result['item_results'] ?? null) ? $result['item_results'] : array();
        if ($selected_entity_keys === array() || $item_results === array()) {
            return 'completed_incomplete_evidence';
        }

        $terminal_statuses = array('verified', 'skipped_no_change', 'already_synced', 'failed_readback', 'failed_write', 'failed', 'skipped', 'skipped_with_reason');
        $seen_entities = array();
        $has_unknown = false;
        foreach ($item_results as $item_result) {
            if (!is_array($item_result)) {
                $has_unknown = true;
                continue;
            }
            $entity_key = sanitize_text_field((string) ($item_result['entity_key'] ?? ''));
            $status = sanitize_key((string) ($item_result['status'] ?? ''));
            if ($entity_key === '' || !in_array($status, $terminal_statuses, true)) {
                $has_unknown = true;
                continue;
            }
            $seen_entities[$entity_key] = true;
        }
        foreach ($selected_entity_keys as $entity_key) {
            if (!isset($seen_entities[$entity_key])) {
                return 'completed_incomplete_evidence';
            }
        }
        if ($has_unknown) {
            return 'completed_incomplete_evidence';
        }
        if (intval($result['failed_count'] ?? 0) > 0 && intval($result['verified_count'] ?? 0) === 0) {
            return 'failed';
        }
        if (intval($result['failed_count'] ?? 0) > 0 || intval($result['skipped_count'] ?? 0) > 0) {
            return 'completed_partial';
        }
        return intval($result['verified_count'] ?? 0) > 0 ? 'completed_verified' : 'completed_partial';
    }

    private function aysa_agent8v3_onpage_meta_verified_item_results($item_id, $entity_key, $apply, $item = array()) {
        $apply = is_array($apply) ? $apply : array();
        $item = is_array($item) ? $item : array();
        $metadata_readback = is_array($apply['metadata_readback'] ?? null) ? $apply['metadata_readback'] : array();
        $field_statuses = is_array($metadata_readback['field_status'] ?? null) ? $metadata_readback['field_status'] : array();
        $fields = array(
            'meta_title' => array(
                'old' => trim((string) ($apply['previous_meta_title'] ?? '')),
                'new' => trim((string) ($apply['meta_title'] ?? '')),
                'readback_key' => 'title',
            ),
            'meta_description' => array(
                'old' => trim((string) ($apply['previous_meta_description'] ?? '')),
                'new' => trim((string) ($apply['meta_description'] ?? '')),
                'readback_key' => 'meta_description',
            ),
        );
        $results = array();
        foreach ($fields as $field => $values) {
            if ($values['new'] === '') {
                continue;
            }
            $changed = $values['old'] !== $values['new'];
            $field_readback = is_array($field_statuses[$values['readback_key']] ?? null)
                ? $field_statuses[$values['readback_key']]
                : array();
            $provider_matched = (string) ($field_readback['provider_status'] ?? '') === 'matched';
            $public_matched = (string) ($field_readback['public_status'] ?? '') === 'matched';
            $verified = $provider_matched && $public_matched;
            $results[] = array(
                'item_id' => sanitize_text_field((string) $item_id) . ':' . $field,
                'entity_key' => sanitize_text_field((string) $entity_key),
                'provider_output_id' => sanitize_text_field((string) ($item['provider_output_id'] ?? '')),
                'proposal_hash' => sanitize_text_field((string) ($item['proposal_hash'] ?? '')),
                'history_id' => sanitize_text_field((string) ($apply['history_id'] ?? '')),
                'field' => $field,
                'status' => !$changed ? 'skipped_no_change' : ($verified ? 'verified' : 'failed_readback'),
                'changed' => $changed,
                'verified' => $verified,
                'reason' => !$changed ? 'no_change' : ($verified ? '' : 'provider_or_public_readback_mismatch'),
                'old_value' => $values['old'],
                'proposed_value' => $values['new'],
                'actual_value' => trim((string) ($field_readback['public'] ?? $field_readback['provider'] ?? '')),
                'provider_status' => (string) ($field_readback['provider_status'] ?? 'not_checked'),
                'public_status' => (string) ($field_readback['public_status'] ?? 'not_checked'),
            );
        }

        if ($results !== array()) {
            return $results;
        }
        return array(array(
            'item_id' => sanitize_text_field((string) $item_id),
            'entity_key' => sanitize_text_field((string) $entity_key),
            'provider_output_id' => sanitize_text_field((string) ($item['provider_output_id'] ?? '')),
            'proposal_hash' => sanitize_text_field((string) ($item['proposal_hash'] ?? '')),
            'history_id' => sanitize_text_field((string) ($apply['history_id'] ?? '')),
            'status' => 'skipped_no_change',
            'changed' => false,
            'verified' => true,
            'reason' => 'no_metadata_fields',
        ));
    }

    private function aysa_agent8v3_log_onpage_execution($plan, $result) {
        $events = get_option('aysa_agent8v3_onpage_executions', array());
        $events = is_array($events) ? $events : array();
        array_unshift($events, array(
            'action' => 'execute_onpage_plan',
            'phase' => 'post_execution',
            'section' => sanitize_key((string) ($plan['section'] ?? '')),
            'operation' => sanitize_key((string) ($plan['operation'] ?? '')),
            'run_id' => intval($result['run_id'] ?? $plan['run_id'] ?? 0),
            'source_run_id' => intval($result['source_run_id'] ?? $plan['source_run_id'] ?? $plan['run_id'] ?? 0),
            'execution_id' => sanitize_text_field((string) ($result['execution_id'] ?? $result['execution_ref'] ?? '')),
            'action_reference' => sanitize_text_field((string) ($result['action_reference'] ?? $result['action_ref'] ?? '')),
            'execution_ref' => sanitize_text_field((string) ($result['execution_ref'] ?? '')),
            'action_ref' => sanitize_text_field((string) ($result['action_ref'] ?? '')),
            'confirmation_kind' => sanitize_key((string) ($result['confirmation_kind'] ?? $plan['confirmation_kind'] ?? '')),
            'user_confirmed' => !empty($result['user_confirmed']) || !empty($plan['user_confirmed']) || !empty($plan['confirmed_by_user']),
            'selected_entity_keys' => array_values(array_slice(array_map('sanitize_text_field', is_array($result['selected_entity_keys'] ?? null) ? $result['selected_entity_keys'] : array()), 0, 500)),
            'selected_provider_output_ids' => array_values(array_slice(array_map('sanitize_text_field', is_array($result['selected_provider_output_ids'] ?? null) ? $result['selected_provider_output_ids'] : array()), 0, 500)),
            'selection_hash' => sanitize_text_field((string) ($result['selection_hash'] ?? '')),
            'analysis_bundle_id' => sanitize_text_field((string) ($result['analysis_bundle_id'] ?? '')),
            'source_metadata_run_id' => intval($result['source_metadata_run_id'] ?? 0),
            'proposal_hashes' => is_array($result['proposal_hashes'] ?? null) ? $result['proposal_hashes'] : array(),
            'old_value_snapshot' => is_array($result['old_value_snapshot'] ?? null) ? $result['old_value_snapshot'] : array(),
            'proposed_value_snapshot' => is_array($result['proposed_value_snapshot'] ?? null) ? $result['proposed_value_snapshot'] : array(),
            'max_total_cost_cents' => intval($result['max_total_cost_cents'] ?? 0),
            'durable_identity_status' => sanitize_key((string) ($result['durable_identity_status'] ?? 'incomplete')),
            'pre_execution_snapshot_persisted' => !empty($result['pre_execution_snapshot_persisted']),
            'post_execution_snapshot_persisted' => !empty($result['post_execution_snapshot_persisted']),
            'terminal_snapshot_persisted' => !empty($result['terminal_snapshot_persisted']),
            'notification_action_reference' => sanitize_text_field((string) ($result['notification_action_reference'] ?? '')),
            'notification_status' => sanitize_key((string) ($result['notification_status'] ?? 'not_sent')),
            'accepted_count' => intval($result['accepted_count'] ?? 0),
            'applied_count' => intval($result['applied_count'] ?? 0),
            'verified_count' => intval($result['verified_count'] ?? 0),
            'failed_count' => intval($result['failed_count'] ?? 0),
            'skipped_count' => intval($result['skipped_count'] ?? 0),
            'already_applied_count' => intval($result['already_applied_count'] ?? 0),
            'payload_hash' => sanitize_text_field((string) ($result['payload_hash'] ?? $plan['payload_hash'] ?? '')),
            'idempotency_key' => sanitize_text_field((string) ($result['idempotency_key'] ?? $plan['idempotency_key'] ?? '')),
            'status' => sanitize_key((string) ($result['status'] ?? 'unknown')),
            'completion_state' => sanitize_key((string) ($result['completion_state'] ?? $result['status'] ?? 'unknown')),
            'billing_action_key' => sanitize_key((string) ($result['billing_action_key'] ?? 'onpage_plan_execution_bridge')),
            'billing_status' => sanitize_key((string) ($result['billing_status'] ?? '')),
            'billing_policy' => sanitize_text_field((string) ($result['billing_policy'] ?? 'verified_changed_onpage_fields_only_cost_x5')),
            'billing_unit_cents' => intval($result['billing_unit_cents'] ?? 5),
            'billing_cost_cents' => intval($result['billing_cost_cents'] ?? $result['actual_credits_cents'] ?? 0),
            'actual_credits_cents' => intval($result['actual_credits_cents'] ?? $result['billing_cost_cents'] ?? 0),
            'billed_units' => intval($result['billed_units'] ?? 0),
            'charged_credits' => intval($result['charged_credits'] ?? $result['actual_credits_cents'] ?? 0),
            'billing_ledger_reference' => sanitize_text_field((string) ($result['billing_ledger_reference'] ?? '')),
            'billing' => is_array($result['billing'] ?? null) ? $result['billing'] : array(),
            'ai_visibility_intervention_callback' => is_array($result['ai_visibility_intervention_callback'] ?? null)
                ? $result['ai_visibility_intervention_callback']
                : array(),
            'error' => isset($result['error']) ? sanitize_text_field((string) $result['error']) : null,
            'contract_errors' => array_values(array_slice(array_map('sanitize_text_field', is_array($result['contract_errors'] ?? null) ? $result['contract_errors'] : array()), 0, 20)),
            'errors' => array_values(array_slice(is_array($result['errors'] ?? null) ? $result['errors'] : array(), 0, 20)),
            'item_results' => array_values(array_slice(is_array($result['item_results'] ?? null) ? $result['item_results'] : array(), 0, 500)),
            'timestamp' => current_time('mysql'),
        ));
        update_option('aysa_agent8v3_onpage_executions', array_slice($events, 0, 50), false);
    }

    private function aysa_agent8v3_resolve_onpage_execution_source_run_id_from_plan($plan, $request_payload, $items) {
        $plan = is_array($plan) ? $plan : array();
        $request_payload = is_array($request_payload) ? $request_payload : array();
        $items = is_array($items) ? $items : array();
        $plan_candidates = array_values(array_unique(array_filter(array(
            intval($plan['source_run_id'] ?? 0),
            intval($plan['run_id'] ?? 0),
        ), static fn(int $value): bool => $value > 0)));
        if ($plan_candidates !== array()) {
            return count($plan_candidates) === 1 ? intval($plan_candidates[0]) : 0;
        }
        $request_candidates = array_values(array_unique(array_filter(array(
            intval($request_payload['source_run_id'] ?? 0),
            intval($request_payload['run_id'] ?? 0),
        ), static fn(int $value): bool => $value > 0)));
        if ($request_candidates !== array()) {
            return count($request_candidates) === 1 ? intval($request_candidates[0]) : 0;
        }
        $provider_candidates = array();
        foreach ($items as $item) {
            if (!is_array($item)) {
                continue;
            }
            $provider_output_id = trim((string) ($item['provider_output_id'] ?? ''));
            if (preg_match('/^(\d+):/', $provider_output_id, $matches) === 1) {
                $provider_candidates[] = intval($matches[1]);
            }
        }
        $provider_candidates = array_values(array_unique(array_filter($provider_candidates, static fn(int $value): bool => $value > 0)));
        return count($provider_candidates) === 1 ? intval($provider_candidates[0]) : 0;
    }

    private function aysa_agent8v3_latest_onpage_execution_snapshot() {
        $events = get_option('aysa_agent8v3_onpage_executions', array());
        $events = is_array($events) ? $events : array();
        // Events are inserted newest-first. The default report must describe
        // the most recent terminal attempt, even when an older attempt has a
        // more favorable verified status; older successful reports remain
        // available through the historical selector.
        foreach ($events as $event) {
            if (is_array($event) && $this->aysa_agent8v3_is_onpage_execution_history_event($event)) {
                return $event;
            }
        }
        return array();
    }

    private function aysa_agent8v3_is_onpage_execution_history_event($event) {
        if (!is_array($event)) {
            return false;
        }
        if ((string) ($event['phase'] ?? '') === 'pre_execution') {
            return false;
        }

        $operation = sanitize_key((string) ($event['operation'] ?? ''));
        $section = sanitize_key((string) ($event['section'] ?? ''));
        $has_selection_evidence = !empty($event['selected_entity_keys'])
            || !empty($event['selected_provider_output_ids'])
            || !empty($event['item_results']);
        if ($operation === 'apply_meta'
            || ($section === 'content' && $operation === 'apply_rewrite')
        ) {
            return $has_selection_evidence;
        }

        // Existing metadata applies can be persisted through the bridge
        // contract instead of the workspace operation name. They remain the
        // same read-only On Page evidence and must stay selectable in history.
        $bridge_operations = array(
            'metadata_apply',
            'onpage_metadata_apply',
            'technical_metadata_apply',
            'technical_metadata_apply_bridge',
        );
        $confirmation_kind = sanitize_key((string) ($event['confirmation_kind'] ?? ''));
        $billing_action_key = sanitize_key((string) ($event['billing_action_key'] ?? ''));
        $write_class = sanitize_key((string) ($event['write_class'] ?? ''));
        $has_metadata_contract = in_array($operation, $bridge_operations, true)
            || $confirmation_kind === 'onpage_seo_apply_accepted_bundle'
            || $billing_action_key === 'onpage_plan_execution_bridge'
            || $write_class === 'onpage_metadata_apply';
        if (!$has_metadata_contract) {
            return false;
        }

        return $has_selection_evidence;
    }

    private function aysa_agent8v3_find_onpage_execution_snapshot($source_run_id = 0, $idempotency_key = '', $snapshot_identity = '') {
        $source_run_id = intval($source_run_id);
        $idempotency_key = sanitize_text_field((string) $idempotency_key);
        $snapshot_identity = sanitize_text_field((string) $snapshot_identity);
        if ($source_run_id <= 0 && $idempotency_key === '' && $snapshot_identity === '') {
            return array();
        }
        $events = get_option('aysa_agent8v3_onpage_executions', array());
        $matches = array();
        foreach (is_array($events) ? $events : array() as $event) {
            if (!is_array($event)) {
                continue;
            }
            if ((string) ($event['phase'] ?? '') === 'pre_execution') {
                continue;
            }
            if ($snapshot_identity !== '') {
                $identity = $this->aysa_agent8v3_onpage_execution_snapshot_identity($event);
                if ((string) ($identity['snapshot_identity'] ?? '') === $snapshot_identity) {
                    return $event;
                }
                continue;
            }
            $event_source_run_id = intval($event['source_run_id'] ?? 0);
            $event_idempotency_key = sanitize_text_field((string) ($event['idempotency_key'] ?? ''));
            if (($source_run_id > 0 && $event_source_run_id === $source_run_id)
                || ($idempotency_key !== '' && hash_equals($idempotency_key, $event_idempotency_key))) {
                $matches[] = $event;
            }
        }
        // A source run can contain multiple selections. Never silently choose
        // the first event when the caller did not provide a stable identity.
        return count($matches) === 1 ? $matches[0] : array();
    }

    private function aysa_agent8v3_onpage_execution_snapshot_identity($snapshot) {
        $snapshot = is_array($snapshot) ? $snapshot : array();
        $entity_keys = array_values(array_unique(array_filter(array_map('sanitize_text_field', is_array($snapshot['selected_entity_keys'] ?? null) ? $snapshot['selected_entity_keys'] : array()))));
        $provider_output_ids = array_values(array_unique(array_filter(array_map('sanitize_text_field', is_array($snapshot['selected_provider_output_ids'] ?? null) ? $snapshot['selected_provider_output_ids'] : array()))));
        foreach (is_array($snapshot['item_results'] ?? null) ? $snapshot['item_results'] : array() as $item) {
            if (!is_array($item)) {
                continue;
            }
            $entity_key = sanitize_text_field((string) ($item['entity_key'] ?? ''));
            $provider_output_id = sanitize_text_field((string) ($item['provider_output_id'] ?? ''));
            if ($entity_key !== '') {
                $entity_keys[] = $entity_key;
            }
            if ($provider_output_id !== '') {
                $provider_output_ids[] = $provider_output_id;
            }
        }
        $entity_keys = array_values(array_unique(array_filter($entity_keys)));
        $provider_output_ids = array_values(array_unique(array_filter($provider_output_ids)));
        sort($entity_keys, SORT_STRING);
        sort($provider_output_ids, SORT_STRING);
        $selection_hash = hash('sha256', wp_json_encode(array(
            'selected_entity_keys' => $entity_keys,
            'selected_provider_output_ids' => $provider_output_ids,
        ), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
        $source_run_id = intval($snapshot['source_run_id'] ?? $snapshot['run_id'] ?? 0);
        $action_reference = sanitize_text_field((string) ($snapshot['action_ref'] ?? $snapshot['execution_ref'] ?? ''));
        $created_at = sanitize_text_field((string) ($snapshot['timestamp'] ?? $snapshot['created_at'] ?? $snapshot['updated_at'] ?? ''));
        $completion_state = sanitize_key((string) ($snapshot['completion_state'] ?? $snapshot['status'] ?? 'unknown'));
        $identity = hash('sha256', wp_json_encode(array(
            'source_run_id' => $source_run_id,
            'action_reference' => $action_reference,
            'selection_hash' => $selection_hash,
            'created_at' => $created_at,
            'completion_state' => $completion_state,
        ), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
        return array(
            'snapshot_identity' => $identity,
            'selection_hash' => $selection_hash,
            'source_run_id' => $source_run_id,
            'action_reference' => $action_reference,
            'created_at' => $created_at,
            'completion_state' => $completion_state,
            'selected_entity_keys' => $entity_keys,
            'selected_provider_output_ids' => $provider_output_ids,
        );
    }

    private function aysa_agent8v3_onpage_execution_history_list($fallback_snapshot = array()) {
        $events = get_option('aysa_agent8v3_onpage_executions', array());
        $events = is_array($events) ? $events : array();
        $reports = array();
        $seen = array();
        $seen_semantic = array();
        $build_report = function ($event) {
            if (!is_array($event)) {
                return array();
            }
            if (!$this->aysa_agent8v3_is_onpage_execution_history_event($event)) {
                return array();
            }
            $identity = $this->aysa_agent8v3_onpage_execution_snapshot_identity($event);
            $snapshot_id = (string) ($identity['snapshot_identity'] ?? '');
            if ($snapshot_id === '') {
                return array();
            }
            $selected_entity_keys = is_array($identity['selected_entity_keys'] ?? null) ? $identity['selected_entity_keys'] : array();
            $item_results = is_array($event['item_results'] ?? null) ? $event['item_results'] : array();
            $incomplete_count = 0;
            $verified_count = intval($event['verified_count'] ?? 0);
            foreach ($item_results as $item) {
                if (!is_array($item)) {
                    $incomplete_count++;
                    continue;
                }
                $status = sanitize_key((string) ($item['status'] ?? ''));
                if ($status === 'missing_execution_evidence') {
                    $incomplete_count++;
                }
            }
            if (($identity['completion_state'] ?? '') === 'completed_incomplete_evidence' && $incomplete_count === 0) {
                $incomplete_count = count($selected_entity_keys);
            }
            $source_run_id = intval($identity['source_run_id'] ?? 0);
            $completion_state = sanitize_key((string) ($identity['completion_state'] ?? 'unknown'));
            return array(
                'snapshot_identity' => $snapshot_id,
                'selection_hash' => (string) ($identity['selection_hash'] ?? ''),
                'label' => $source_run_id > 0
                    ? ('Run ' . $source_run_id . ' — ' . count($selected_entity_keys) . ' rânduri — ' . ($completion_state === 'completed_incomplete_evidence' ? 'verificare manuală necesară' : $this->aysa_agent8v3_onpage_execution_history_label($completion_state)))
                    : ('Raport istoric — ' . count($selected_entity_keys) . ' rânduri'),
                'source_run_id' => $source_run_id,
                'row_count' => count($selected_entity_keys),
                'selected_row_count' => count($selected_entity_keys),
                'applied_verified_count' => $verified_count,
                'incomplete_evidence_count' => $incomplete_count,
                'created_at' => (string) ($identity['created_at'] ?? ''),
                'completion_verdict' => $completion_state,
                'action_reference' => (string) ($identity['action_reference'] ?? ''),
                'read_only' => true,
            );
        };
        foreach ($events as $event) {
            $report = $build_report($event);
            $snapshot_id = (string) ($report['snapshot_identity'] ?? '');
            if ($snapshot_id === '') {
                continue;
            }
            // A single logical execution can have several technical history
            // records (retry timestamps/action references) written for the
            // same source run and selected rows. Keep distinct terminal
            // outcomes visible, but collapse duplicate records for the same
            // semantic report so the client can select the report reliably.
            $semantic_key = implode('|', array(
                (string) ($report['source_run_id'] ?? 0),
                (string) ($report['selection_hash'] ?? ''),
                (string) ($report['completion_verdict'] ?? 'unknown'),
            ));
            if (($report['source_run_id'] ?? 0) <= 0 && ($report['selection_hash'] ?? '') === '') {
                $semantic_key .= '|' . (string) ($report['action_reference'] ?? '');
            }
            if (isset($seen[$snapshot_id]) || isset($seen_semantic[$semantic_key])) {
                continue;
            }
            $seen[$snapshot_id] = true;
            $seen_semantic[$semantic_key] = true;
            $reports[] = $report;
        }

        // The current read-only execution report can be hydrated from the
        // existing projection snapshot even when the local history option was
        // not populated by the older bridge. Reuse that evidence, but never
        // synthesize a report without the same concrete history markers.
        $fallback_report = $build_report($fallback_snapshot);
        $fallback_id = (string) ($fallback_report['snapshot_identity'] ?? '');
        $fallback_semantic_key = implode('|', array(
            (string) ($fallback_report['source_run_id'] ?? 0),
            (string) ($fallback_report['selection_hash'] ?? ''),
            (string) ($fallback_report['completion_verdict'] ?? 'unknown'),
        ));
        if (($fallback_report['source_run_id'] ?? 0) <= 0 && ($fallback_report['selection_hash'] ?? '') === '') {
            $fallback_semantic_key .= '|' . (string) ($fallback_report['action_reference'] ?? '');
        }
        if ($fallback_id !== '' && !isset($seen[$fallback_id]) && !isset($seen_semantic[$fallback_semantic_key])) {
            $reports[] = $fallback_report;
        }
        return array_values(array_slice($reports, 0, 50));
    }

    private function aysa_agent8v3_onpage_execution_history_label($completion_state) {
        return match (sanitize_key((string) $completion_state)) {
            'completed_verified' => 'aplicat și verificat',
            'completed_partial' => 'finalizat parțial',
            'completed_incomplete_evidence' => 'verificare manuală necesară',
            'rejected_stale_selection' => 'selecție respinsă',
            'failed' => 'eșuat',
            default => 'review disponibil',
        };
    }

    private function aysa_agent8v3_response_onpage_execution_snapshot($response) {
        if (!is_array($response)) {
            return array();
        }
        if (is_array($response['onpage_execution'] ?? null)
            && sanitize_key((string) ($response['onpage_execution']['section'] ?? '')) === 'content'
            && sanitize_key((string) ($response['onpage_execution']['operation'] ?? '')) === 'apply_rewrite'
        ) {
            return $response['onpage_execution'];
        }
        if (is_array($response['projection']['onpage_execution'] ?? null)
            && sanitize_key((string) ($response['projection']['onpage_execution']['section'] ?? '')) === 'content'
            && sanitize_key((string) ($response['projection']['onpage_execution']['operation'] ?? '')) === 'apply_rewrite'
        ) {
            return $response['projection']['onpage_execution'];
        }
        $state_execution = array();
        if (is_array($response['state']['data']['onpage_seo']['execution'] ?? null)) {
            $state_execution = $response['state']['data']['onpage_seo']['execution'];
        }
        if (is_array($state_execution['meta_apply'] ?? null)) {
            return $state_execution['meta_apply'];
        }
        if ($state_execution !== array()) {
            return $state_execution;
        }
        if (is_array($response['onpage_execution'] ?? null)) {
            return $response['onpage_execution'];
        }
        if (is_array($response['projection']['onpage_execution'] ?? null)) {
            return $response['projection']['onpage_execution'];
        }
        return array();
    }

    private function aysa_agent8v3_onpage_execution_snapshot_is_preferred($candidate, $current) {
        if (!is_array($candidate) || $candidate === array()) {
            return false;
        }
        $candidate_operation = sanitize_key((string) ($candidate['operation'] ?? ''));
        $candidate_section = sanitize_key((string) ($candidate['section'] ?? ''));
        $candidate_supported = $candidate_operation === 'apply_meta'
            || ($candidate_section === 'content' && $candidate_operation === 'apply_rewrite');
        if (!$candidate_supported) {
            return false;
        }
        if (!is_array($current) || $current === array()) {
            return true;
        }
        $current_operation = sanitize_key((string) ($current['operation'] ?? ''));
        $current_section = sanitize_key((string) ($current['section'] ?? ''));
        if ($candidate_section === 'content' && $candidate_operation === 'apply_rewrite'
            && !($current_section === 'content' && $current_operation === 'apply_rewrite')
        ) {
            return true;
        }

        $candidate_status = sanitize_key((string) ($candidate['status'] ?? ''));
        $current_status = sanitize_key((string) ($current['status'] ?? ''));
        $candidate_verified = intval($candidate['verified_count'] ?? 0);
        $candidate_applied = intval($candidate['applied_count'] ?? 0);
        $candidate_actual = intval($candidate['actual_credits_cents'] ?? $candidate['billing_cost_cents'] ?? 0);
        $current_verified = intval($current['verified_count'] ?? 0);
        $current_applied = intval($current['applied_count'] ?? 0);
        $current_actual = intval($current['actual_credits_cents'] ?? $current['billing_cost_cents'] ?? 0);

        if (in_array($candidate_status, array('verified', 'already_synced'), true) && !in_array($current_status, array('verified', 'already_synced'), true)) {
            return true;
        }
        if (($candidate_verified > $current_verified) || ($candidate_actual > $current_actual)) {
            return true;
        }
        if ($candidate_applied > 0 && $current_applied === 0 && $current_status === 'failed') {
            return true;
        }

        return false;
    }

    private function aysa_agent8v3_normalize_onpage_execution_snapshot($snapshot, $project_id) {
        if (!is_array($snapshot) || $snapshot === array()) {
            return array();
        }
        if (sanitize_key((string) ($snapshot['operation'] ?? '')) !== 'apply_meta') {
            return $snapshot;
        }
        if (sanitize_key((string) ($snapshot['status'] ?? '')) !== 'failed') {
            return $snapshot;
        }
        $errors = is_array($snapshot['errors'] ?? null) ? array_map('sanitize_key', $snapshot['errors']) : array();
        if (!in_array('meta_package_missing', $errors, true)) {
            return $snapshot;
        }

        $item_results = is_array($snapshot['item_results'] ?? null) ? array_values($snapshot['item_results']) : array();
        $entity_key = '';
        foreach ($item_results as $item) {
            if (!is_array($item)) {
                continue;
            }
            $entity_key = sanitize_text_field((string) ($item['entity_key'] ?? ''));
            if ($entity_key !== '') {
                break;
            }
        }
        if ($entity_key === '') {
            return $snapshot;
        }

        $service = $this->aysa_onpage_workspace_service();
        if (!$service || !method_exists($service, 'build_selection_meta_for_entity')) {
            return $snapshot;
        }
        $entity_meta = $service->build_selection_meta_for_entity(intval($project_id), $entity_key);
        if (sanitize_key((string) ($entity_meta['meta_state'] ?? '')) !== 'applied') {
            return $snapshot;
        }

        $snapshot['status'] = 'already_synced';
        $snapshot['applied_count'] = 0;
        $snapshot['verified_count'] = 0;
        $snapshot['failed_count'] = 0;
        $snapshot['skipped_count'] = max(1, intval($snapshot['skipped_count'] ?? 0));
        $snapshot['errors'] = array();
        $snapshot['billing_status'] = 'skipped';
        $snapshot['billing_cost_cents'] = 0;
        $snapshot['actual_credits_cents'] = 0;
        $snapshot['item_results'] = array(array(
            'item_id' => $entity_key,
            'entity_key' => $entity_key,
            'status' => 'already_synced',
            'changed' => false,
            'verified' => true,
            'reason' => 'meta_already_applied',
        ));
        return $snapshot;
    }

    private function aysa_agent8v3_prepare_local_technical_fix_execution_plan($plan, $request_payload) {
        $plan = is_array($plan) ? $plan : array();
        $request_payload = is_array($request_payload) ? $request_payload : array();
        $action_payload = isset($request_payload['payload']) && is_array($request_payload['payload']) ? $request_payload['payload'] : array();
        $module = sanitize_key((string) ($plan['module'] ?? $action_payload['module'] ?? ''));
        if ($module === '' || $action_payload === array()) {
            return $plan;
        }

        $has_fixes = isset($plan['payload']['fixes']) && is_array($plan['payload']['fixes']) && $plan['payload']['fixes'] !== array();
        if ($has_fixes) {
            return $plan;
        }

        $selected_items = array();
        if (isset($action_payload['accepted_fixes']) && is_array($action_payload['accepted_fixes'])) {
            $selected_items = array_values(array_filter($action_payload['accepted_fixes'], 'is_array'));
        }
        if ($selected_items === array() && isset($action_payload['selected_items']) && is_array($action_payload['selected_items'])) {
            $selected_items = array_values(array_filter($action_payload['selected_items'], 'is_array'));
        }
        if ($selected_items === array()) {
            return $plan;
        }

        if (!isset($plan['payload']) || !is_array($plan['payload'])) {
            $plan['payload'] = array();
        }
        $plan['payload']['fixes'] = $selected_items;
        $plan['_local_payload_hydrated'] = true;

        $plan['module'] = $module;
        $plan['accepted_count'] = intval($plan['accepted_count'] ?? count($selected_items));
        if (empty($plan['payload_hash']) && !empty($action_payload['payload_hash'])) {
            $plan['payload_hash'] = sanitize_text_field((string) $action_payload['payload_hash']);
        }
        if (empty($plan['payload_hash'])) {
            $plan['payload_hash'] = substr(hash('sha256', wp_json_encode($selected_items)), 0, 32);
        }
        if (empty($plan['idempotency_key']) && !empty($action_payload['idempotency_key'])) {
            $plan['idempotency_key'] = sanitize_text_field((string) $action_payload['idempotency_key']);
        }
        if (empty($plan['operation']) && !empty($action_payload['operation'])) {
            $plan['operation'] = sanitize_key((string) $action_payload['operation']);
        }
        if (empty($plan['confirmation_kind']) && !empty($action_payload['confirmation_kind'])) {
            $plan['confirmation_kind'] = sanitize_key((string) $action_payload['confirmation_kind']);
        }
        $plan['user_confirmed'] = !empty($plan['user_confirmed']) || !empty($action_payload['user_confirmed']) || !empty($action_payload['confirmed_by_user']);
        $plan['confirmed_by_user'] = !empty($plan['confirmed_by_user']) || !empty($action_payload['confirmed_by_user']) || !empty($action_payload['user_confirmed']);
        $plan['verification_required'] = !empty($plan['verification_required']) || !empty($action_payload['verification_required']);
        $plan['dry_run'] = array_key_exists('dry_run', $action_payload) ? !empty($action_payload['dry_run']) : !empty($plan['dry_run']);
        $plan['writes_enabled'] = array_key_exists('writes_enabled', $action_payload) ? !empty($action_payload['writes_enabled']) : !empty($plan['writes_enabled']);

        foreach (array('product_gates', 'billing_action_key', 'billing_status', 'billing_policy', 'billing_unit_cents', 'max_cost_cents') as $field) {
            $request_field_is_authoritative = $module === 'internal_linking' && array_key_exists($field, $action_payload);
            if ($request_field_is_authoritative || ((!isset($plan[$field]) || $plan[$field] === '' || $plan[$field] === array()) && array_key_exists($field, $action_payload))) {
                $plan[$field] = $action_payload[$field];
            }
        }

        return $plan;
    }

    private function aysa_agent8v3_apply_technical_fix_execution_plan($plan, $request_payload) {
        $plan = is_array($plan) ? $plan : array();
        $idempotency_key = sanitize_text_field((string) ($plan['idempotency_key'] ?? ''));
        $payload_hash = sanitize_text_field((string) ($plan['payload_hash'] ?? ''));
        $module = sanitize_key((string) ($plan['module'] ?? ''));
        $fixes = is_array($plan['payload']['fixes'] ?? null) ? $plan['payload']['fixes'] : array();
        $result = array(
            'action' => 'execute_technical_fix_plan',
            'module' => $module,
            'payload_hash' => $payload_hash,
            'accepted_count' => count($fixes),
            'applied_count' => 0,
            'failed_count' => 0,
            'manual_needed_count' => 0,
            'skipped_count' => 0,
            'status' => 'skipped',
            'errors' => array(),
            'option_keys' => array('aysa_agent8v3_technical_fix_execution_idempotency', 'aysa_agent8v3_technical_fix_executions'),
        );
        if (in_array($module, array('image_alt', 'metadata', 'canonical', 'image_optimization'), true) && intval($plan['task_id'] ?? 0) > 0) {
            $result['status'] = sanitize_key((string) ($plan['status'] ?? 'queued'));
            $result['queued_count'] = intval($plan['accepted_count'] ?? 0);
            $result['run_id'] = intval($plan['run_id'] ?? 0);
            $result['task_id'] = intval($plan['task_id'] ?? 0);
            $result['worker_ref'] = sanitize_text_field((string) ($plan['worker_ref'] ?? ''));
            $result['worker_status'] = sanitize_key((string) ($plan['status'] ?? 'queued'));
            $result['billing_status'] = sanitize_key((string) ($plan['billing_status'] ?? 'disabled_for_rollout'));
            $result['billing_action_key'] = sanitize_key((string) ($plan['billing_action_key'] ?? ''));
            $this->aysa_agent8v3_log_technical_fix_execution($plan, $result);
            return $result;
        }
        if ($idempotency_key === '' || $module === '' || empty($fixes)) {
            $result['status'] = 'rejected';
            $result['error'] = 'technical_fix_execution_plan_invalid';
            $this->aysa_agent8v3_log_technical_fix_execution($plan, $result);
            return $result;
        }

        $applied = get_option('aysa_agent8v3_technical_fix_execution_idempotency', array());
        $applied = is_array($applied) ? $applied : array();
        if (!empty($applied[$idempotency_key])) {
            $result = array_merge($result, is_array($applied[$idempotency_key]) ? $applied[$idempotency_key] : array());
            $result['status'] = 'already_synced';
            $this->aysa_agent8v3_log_technical_fix_execution($plan, $result);
            return $result;
        }

        $project_id = intval($request_payload['wp_project_id'] ?? get_option('aysa_project_id', 0));
        if ($project_id <= 0) {
            $result['status'] = 'failed';
            $result['error'] = 'project_id_required';
            $this->aysa_agent8v3_log_technical_fix_execution($plan, $result);
            return $result;
        }

        if ($module === 'schema') {
            $result = $this->aysa_agent8v3_enqueue_schema_fix_execution_job($project_id, $plan, $fixes, $result);
            $applied[$idempotency_key] = array(
                'module' => $module,
                'payload_hash' => $payload_hash,
                'accepted_count' => count($fixes),
                'applied_count' => intval($result['applied_count'] ?? 0),
                'failed_count' => intval($result['failed_count'] ?? 0),
                'manual_needed_count' => intval($result['manual_needed_count'] ?? 0),
                'queued_count' => intval($result['queued_count'] ?? 0),
                'job_id' => sanitize_text_field((string) ($result['job_id'] ?? '')),
                'worker_ref' => sanitize_text_field((string) ($result['worker_ref'] ?? '')),
                'worker_status' => sanitize_key((string) ($result['worker_status'] ?? '')),
                'option_keys' => $result['option_keys'],
                'timestamp' => current_time('mysql'),
            );
            update_option('aysa_agent8v3_technical_fix_execution_idempotency', array_slice($applied, -100, null, true), false);
            $this->aysa_agent8v3_log_technical_fix_execution($plan, $result);
            return $result;
        }

        if ($module === 'internal_linking') {
            $result = $this->aysa_agent8v3_apply_internal_linking_fix_execution($project_id, $plan, $fixes, $result);
            if (in_array(sanitize_key((string) ($result['status'] ?? '')), array('verified', 'synced', 'partial_failed', 'skipped', 'manual_review_needed'), true)) {
                $applied[$idempotency_key] = array(
                    'module' => $module,
                    'payload_hash' => $payload_hash,
                    'accepted_count' => count($fixes),
                    'applied_count' => intval($result['applied_count'] ?? 0),
                    'failed_count' => intval($result['failed_count'] ?? 0),
                    'manual_needed_count' => intval($result['manual_needed_count'] ?? 0),
                    'skipped_count' => intval($result['skipped_count'] ?? 0),
                    'option_keys' => $result['option_keys'],
                    'timestamp' => current_time('mysql'),
                );
                update_option('aysa_agent8v3_technical_fix_execution_idempotency', array_slice($applied, -100, null, true), false);
            }
            $this->aysa_agent8v3_log_technical_fix_execution($plan, $result);
            return $result;
        }

        foreach ($fixes as $fix) {
            if (!is_array($fix)) {
                continue;
            }
            $operation = sanitize_key((string) ($fix['operation'] ?? ''));
            $execution_result = $this->aysa_agent8v3_execute_technical_fix($project_id, $module, $operation, $fix);
            if (!empty($execution_result['manual_needed'])) {
                $result['manual_needed_count']++;
            } elseif (!empty($execution_result['applied']) || !empty($execution_result['already_done'])) {
                $result['applied_count']++;
            } else {
                $result['failed_count']++;
                $result['errors'][] = array(
                    'fix_id' => sanitize_text_field((string) ($fix['fix_id'] ?? '')),
                    'reason' => sanitize_key((string) ($execution_result['reason'] ?? 'technical_fix_failed')),
                    'message' => sanitize_text_field((string) ($execution_result['message'] ?? '')),
                );
            }
        }

        $result['status'] = $result['failed_count'] > 0 && $result['applied_count'] === 0 ? 'failed' : 'synced';
        $applied[$idempotency_key] = array(
            'module' => $module,
            'payload_hash' => $payload_hash,
            'accepted_count' => count($fixes),
            'applied_count' => intval($result['applied_count']),
            'failed_count' => intval($result['failed_count']),
            'manual_needed_count' => intval($result['manual_needed_count']),
            'option_keys' => $result['option_keys'],
            'timestamp' => current_time('mysql'),
        );
        update_option('aysa_agent8v3_technical_fix_execution_idempotency', array_slice($applied, -100, null, true), false);
        $this->aysa_agent8v3_log_technical_fix_execution($plan, $result);
        return $result;
    }

    private function aysa_agent8v3_maybe_attach_technical_fix_execution_status($payload) {
        $agent_action = is_array($payload) ? (string) ($payload['agent_action'] ?? ($payload['action'] ?? '')) : '';
        if (!is_array($payload) || $agent_action !== 'refresh_technical_fix_execution_status') {
            return $payload;
        }
        $action_payload = isset($payload['payload']) && is_array($payload['payload']) ? $payload['payload'] : array();
        $module = sanitize_key((string) ($action_payload['module'] ?? ''));
        if ($module !== 'schema' || !empty($action_payload['technical_fix_execution_result'])) {
            return $payload;
        }
        $status = $this->aysa_agent8v3_latest_schema_fix_execution_status($module);
        if (!empty($status)) {
            $action_payload['technical_fix_execution_result'] = $status;
            $payload['payload'] = $action_payload;
        }
        return $payload;
    }

    private function aysa_agent8v3_latest_schema_fix_execution_status($module) {
        $module = sanitize_key((string) $module);
        $applied = get_option('aysa_agent8v3_technical_fix_execution_idempotency', array());
        $applied = is_array($applied) ? array_reverse($applied, true) : array();
        foreach ($applied as $entry) {
            if (!is_array($entry) || sanitize_key((string) ($entry['module'] ?? '')) !== $module) {
                continue;
            }
            $job_id = sanitize_text_field((string) ($entry['job_id'] ?? ''));
            if ($job_id === '') {
                continue;
            }
            return $this->aysa_agent8v3_schema_job_execution_status($job_id, $entry);
        }
        return array();
    }

    private function aysa_agent8v3_schema_job_execution_status($job_id, array $entry = array()) {
        $job_id = sanitize_text_field((string) $job_id);
        $job = $this->aysa_technical_schema_read_job($job_id);
        if (is_array($job) && in_array((string) ($job['status'] ?? ''), array('queued', 'running'), true) && intval($job['lock_until'] ?? 0) <= time()) {
            $job = $this->aysa_technical_schema_process_job($job_id, 4);
        }
        if (!is_array($job)) {
            return array(
                'action' => 'execute_technical_fix_plan',
                'module' => 'schema',
                'payload_hash' => sanitize_text_field((string) ($entry['payload_hash'] ?? '')),
                'status' => 'failed',
                'job_id' => $job_id,
                'worker_ref' => sanitize_text_field((string) ($entry['worker_ref'] ?? '')),
                'worker_status' => 'missing',
                'applied_count' => 0,
                'failed_count' => 1,
                'manual_needed_count' => 0,
                'queued_count' => 0,
                'skipped_count' => 0,
                'errors' => array(array('fix_id' => '', 'reason' => 'schema_job_missing', 'message' => 'Schema job not found.')),
            );
        }

        $status = sanitize_key((string) ($job['status'] ?? 'queued'));
        if (in_array($status, array('completed', 'failed', 'cancelled'), true)) {
            $this->aysa_project_jobs_finalize(
                intval($job['project_id'] ?? 0),
                $this->aysa_project_jobs_make_worker_ref('technical_schema', 'schema_job', (string) ($job['job_id'] ?? $job_id)),
                $status === 'completed' ? 'completed' : 'failed',
                $job,
                (string) ($job['last_error'] ?? '')
            );
        }

        return array(
            'action' => 'execute_technical_fix_plan',
            'module' => 'schema',
            'payload_hash' => sanitize_text_field((string) ($entry['payload_hash'] ?? '')),
            'status' => $status,
            'job_id' => $job_id,
            'worker_ref' => $this->aysa_project_jobs_make_worker_ref('technical_schema', 'schema_job', $job_id),
            'worker_status' => $status,
            'applied_count' => intval($job['successful'] ?? 0),
            'failed_count' => intval($job['failed'] ?? 0),
            'manual_needed_count' => intval(($job['blocked'] ?? 0) + ($job['needs_fields'] ?? 0) + ($job['needs_takeover'] ?? 0)),
            'queued_count' => in_array($status, array('queued', 'running'), true) ? intval($job['total'] ?? 0) : 0,
            'skipped_count' => intval($job['skipped'] ?? 0),
            'errors' => !empty($job['last_error']) ? array(array(
                'fix_id' => '',
                'reason' => 'schema_job_error',
                'message' => sanitize_text_field((string) $job['last_error']),
            )) : array(),
        );
    }

    private function aysa_agent8v3_enqueue_schema_fix_execution_job($project_id, $plan, array $fixes, array $base_result) {
        $items = array();
        $scope_lookup = $this->aysa_technical_schema_scope_lookup();
        foreach ($fixes as $fix) {
            if (!is_array($fix)) {
                continue;
            }
            $operation = sanitize_key((string) ($fix['operation'] ?? ''));
            if ($operation !== 'enqueue_schema_unified_graph_job') {
                $base_result['manual_needed_count']++;
                continue;
            }
            $url = esc_url_raw((string) ($fix['url'] ?? $fix['target_url'] ?? ''));
            if ($url === '') {
                $base_result['failed_count']++;
                $base_result['errors'][] = array(
                    'fix_id' => sanitize_text_field((string) ($fix['fix_id'] ?? '')),
                    'reason' => 'schema_url_required',
                    'message' => 'Schema fix is missing URL.',
                );
                continue;
            }
            $canonical_plan = isset($fix['canonical_graph_plan']) && is_array($fix['canonical_graph_plan']) ? $fix['canonical_graph_plan'] : array();
            $schema_type = sanitize_key((string) ($fix['schema_type'] ?? $canonical_plan['primary_schema_type'] ?? $fix['primary_recommended_node'] ?? 'webpage'));
            $item = array(
                'url' => $url,
                'title' => sanitize_text_field((string) ($fix['title'] ?? $url)),
                'schema_type' => $schema_type,
                'primary_recommended_node' => $schema_type,
                'page_family' => sanitize_key((string) ($fix['page_family'] ?? '')),
                'entity_group' => sanitize_key((string) ($fix['entity_group'] ?? '')),
                'agent8v3_fix_id' => sanitize_text_field((string) ($fix['fix_id'] ?? '')),
                'canonical_graph_plan' => $canonical_plan,
            );
            $item = $this->aysa_technical_schema_resolve_scope_item($item, $scope_lookup);
            if (!empty($item['url'])) {
                $items[$item['url']] = $item;
            }
        }

        if (empty($items)) {
            $base_result['status'] = $base_result['failed_count'] > 0 ? 'failed' : 'skipped';
            $base_result['error'] = $base_result['failed_count'] > 0 ? 'schema_job_items_invalid' : 'schema_job_no_executable_items';
            return $base_result;
        }

        $mode = 'rewrite_recommended';
        $job = $this->aysa_technical_schema_create_job($project_id, $mode, 'selection', array_values($items), 'worker');
        $this->aysa_technical_schema_schedule_job((string) $job['job_id'], 1);

        $base_result['status'] = 'queued';
        $base_result['queued_count'] = count($items);
        $base_result['accepted_count'] = count($fixes);
        $base_result['job_id'] = sanitize_text_field((string) ($job['job_id'] ?? ''));
        $base_result['worker_ref'] = $this->aysa_project_jobs_make_worker_ref('technical_schema', 'schema_job', (string) ($job['job_id'] ?? ''));
        $base_result['worker_status'] = sanitize_key((string) ($job['status'] ?? 'queued'));
        $base_result['option_keys'][] = $this->aysa_technical_schema_job_key((string) ($job['job_id'] ?? ''));
        return $base_result;
    }

    private function aysa_agent8v3_apply_internal_linking_fix_execution($project_id, $plan, array $fixes, array $result) {
        $bridge_payload = array(
            'module' => 'internal_linking',
            'operation' => 'internal_linking_apply_bridge',
            'selected_items' => array_values($fixes),
            'idempotency_key' => sanitize_text_field((string) ($plan['idempotency_key'] ?? '')),
            'payload_hash' => sanitize_text_field((string) ($plan['payload_hash'] ?? '')),
            'parent_technical_run_id' => intval($plan['parent_technical_run_id'] ?? $plan['run_id'] ?? 0),
            'run_id' => intval($plan['run_id'] ?? 0),
            'user_confirmed' => !empty($plan['user_confirmed']) || !empty($plan['confirmed_by_user']),
            'confirmed_by_user' => !empty($plan['confirmed_by_user']) || !empty($plan['user_confirmed']),
            'dry_run' => !empty($plan['dry_run']),
            'writes_enabled' => !empty($plan['writes_enabled']),
            'product_gates' => isset($plan['product_gates']) && is_array($plan['product_gates']) ? $plan['product_gates'] : array(),
            'billing_status' => sanitize_key((string) ($plan['billing_status'] ?? '')),
            'billing_action_key' => sanitize_key((string) ($plan['billing_action_key'] ?? 'agent8v3_internal_linking_apply')),
            'billing_policy' => sanitize_key((string) ($plan['billing_policy'] ?? 'verified_inserted_internal_links_only_cost_x5')),
            'billing_unit_cents' => intval($plan['billing_unit_cents'] ?? 5),
            'max_cost_cents' => intval($plan['max_cost_cents'] ?? (count($fixes) * 5)),
        );
        $bridge_result = $this->aysa_agent8v3_internal_linking_apply_bridge($project_id, $bridge_payload);

        $result['status'] = sanitize_key((string) ($bridge_result['status'] ?? 'failed'));
        $result['payload_hash'] = sanitize_text_field((string) ($bridge_result['payload_hash'] ?? $bridge_payload['payload_hash']));
        $result['accepted_count'] = intval($bridge_result['accepted_count'] ?? count($fixes));
        $result['applied_count'] = intval($bridge_result['applied_count'] ?? 0);
        $result['verified_count'] = intval($bridge_result['verified_count'] ?? 0);
        $result['failed_count'] = intval($bridge_result['failed_count'] ?? 0);
        $result['skipped_count'] = intval($bridge_result['skipped_count'] ?? 0);
        $result['manual_needed_count'] = intval($bridge_result['manual_needed_count'] ?? 0);
        $result['item_results'] = is_array($bridge_result['item_results'] ?? null) ? $bridge_result['item_results'] : array();
        $result['errors'] = !empty($bridge_result['reason']) ? array(sanitize_text_field((string) $bridge_result['reason'])) : array();
        $result['bridge_result'] = array(
            'operation' => 'internal_linking_apply_bridge',
            'status' => $result['status'],
            'reason' => sanitize_key((string) ($bridge_result['reason'] ?? '')),
            'gate_reasons' => is_array($bridge_result['gate_reasons'] ?? null) ? array_values($bridge_result['gate_reasons']) : array(),
        );
        if (isset($bridge_result['billing_preflight']) && is_array($bridge_result['billing_preflight'])) {
            $billing_preflight = $bridge_result['billing_preflight'];
            $billing_identity = is_array($billing_preflight['billing_identity'] ?? null) ? $billing_preflight['billing_identity'] : array();
            $result['bridge_result']['billing_preflight'] = array(
                'ok' => !empty($billing_preflight['ok']),
                'message' => sanitize_text_field((string) ($billing_preflight['message'] ?? '')),
                'max_cost_cents' => intval($billing_preflight['max_cost_cents'] ?? 0),
                'spendable_cents' => intval($billing_preflight['spendable_cents'] ?? 0),
                'topup_status' => sanitize_key((string) ($billing_preflight['topup_status'] ?? '')),
                'topup_unavailable_reason' => sanitize_text_field((string) ($billing_preflight['topup_unavailable_reason'] ?? '')),
                'billing_identity_remote_present' => !empty($billing_identity['remote_identity_present']),
                'billing_identity_error' => sanitize_key((string) ($billing_identity['error'] ?? '')),
            );
        }
        $result['billing_status'] = sanitize_key((string) ($bridge_result['billing_status'] ?? ''));
        $result['billing_action_key'] = sanitize_key((string) ($bridge_result['billing_action_key'] ?? 'agent8v3_internal_linking_apply'));
        $result['billing_cost_cents'] = intval($bridge_result['billing_cost_cents'] ?? 0);
        $result['actual_credits_cents'] = intval($bridge_result['actual_credits_cents'] ?? 0);
        $result['billing_external_recorded'] = !empty($bridge_result['billing_external_recorded']);
        $result['billing_reference'] = sanitize_text_field((string) ($bridge_result['billing_reference'] ?? ''));
        if (isset($bridge_result['billing'])) {
            $result['billing'] = $bridge_result['billing'];
        }
        return $result;
    }

    private function aysa_agent8v3_execute_technical_fix($project_id, $module, $operation, array $fix) {
        $module = sanitize_key((string) $module);
        $operation = sanitize_key((string) $operation);
        if ($module === 'pagespeed') {
            return array('manual_needed' => true, 'reason' => 'not_executable_yet');
        }
        if ($module === 'sitemap_robots') {
            if ($operation === 'generate_sitemap_index') {
                return $this->aysa_agent8_generate_technical_sitemap($project_id);
            }
            if ($operation === 'apply_robots_policy') {
                return $this->aysa_agent8_restore_technical_robots($project_id);
            }
            if ($operation === 'enforce_sitemap_authority') {
                return $this->aysa_agent8_enforce_aysa_sitemap_authority($project_id);
            }
            return array('manual_needed' => true, 'reason' => 'unsupported_sitemap_robots_operation');
        }
        if (($module === 'schema') && in_array($operation, array('apply_schema', 'disable_schema'), true)) {
            $technical_operation = array(
                'url' => esc_url_raw((string) ($fix['url'] ?? $fix['target_url'] ?? '')),
                'schema_type' => sanitize_key((string) ($fix['schema_type'] ?? 'webpage')),
                'schema_action' => sanitize_key((string) ($fix['schema_action'] ?? 'rewrite_recommended')),
            );
            return $operation === 'disable_schema'
                ? $this->aysa_agent8_disable_technical_schema_operation($project_id, $technical_operation)
                : $this->aysa_agent8_apply_technical_schema_operation($project_id, $technical_operation);
        }
        if (in_array($module, array('redirects', 'site_audit'), true) && $operation === 'apply_redirect') {
            return $this->aysa_agent8_apply_technical_redirect_candidate($project_id, array(
                'source_url' => esc_url_raw((string) ($fix['source_url'] ?? '')),
                'target_url' => esc_url_raw((string) ($fix['target_url'] ?? '')),
            ));
        }
        if ($module === 'redirects' && $operation === 'mark_gone') {
            return $this->aysa_agent8_mark_technical_gone_candidate($project_id, array(
                'source_url' => esc_url_raw((string) ($fix['source_url'] ?? '')),
            ));
        }
        if ($module === 'redirects' && $operation === 'ignore_404') {
            return $this->aysa_agent8_ignore_technical_404_candidate($project_id, array(
                'source_url' => esc_url_raw((string) ($fix['source_url'] ?? '')),
            ));
        }
        if ($module === 'internal_linking' && $operation === 'apply_internal_link') {
            $service = $this->aysa_internal_linking_workspace_service();
            if (!$service || !method_exists($service, 'update_row_action')) {
                return array('applied' => false, 'reason' => 'internal_linking_service_unavailable');
            }
            $opportunity_id = sanitize_text_field((string) ($fix['opportunity_id'] ?? ''));
            if ($opportunity_id === '') {
                return array('applied' => false, 'reason' => 'internal_linking_opportunity_required');
            }
            $row_result = $service->update_row_action($project_id, $opportunity_id, 'apply');
            if (!is_array($row_result) || empty($row_result['ok'])) {
                return array(
                    'applied' => false,
                    'reason' => sanitize_key((string) ($row_result['error'] ?? $row_result['reason'] ?? 'internal_linking_apply_failed')),
                    'message' => sanitize_text_field((string) ($row_result['message'] ?? '')),
                );
            }
            return array('applied' => true, 'operation' => 'apply_internal_link', 'summary' => array('opportunity_id' => $opportunity_id));
        }
        if ($module === 'image_alt' && $operation === 'site_audit_image_alt_apply') {
            $confidence_value = $fix['confidence'] ?? 100;
            $confidence_float = is_numeric($confidence_value) ? (float) $confidence_value : 100;
            $confidence = intval(round($confidence_float > 0 && $confidence_float <= 1 ? $confidence_float * 100 : $confidence_float));
            $row = array(
                'attachment_id' => intval($fix['attachment_id'] ?? $fix['image_id'] ?? 0),
                'image_url' => esc_url_raw((string) ($fix['image_url'] ?? $fix['url'] ?? '')),
                'page_url' => esc_url_raw((string) ($fix['page_url'] ?? $fix['source_url'] ?? '')),
                'decision' => 'rewrite',
                'confidence' => $confidence,
                'proposed_value' => sanitize_text_field((string) ($fix['proposed_value'] ?? $fix['proposed_alt'] ?? '')),
                'old_value' => sanitize_text_field((string) ($fix['old_value'] ?? $fix['current_alt'] ?? '')),
                'is_decorative' => !empty($fix['is_decorative']),
            );
            $package = array(
                'package_id' => 'agent8v3_image_alt_' . intval($project_id) . '_' . substr(md5(wp_json_encode($fix)), 0, 12),
                'project_id' => intval($project_id),
                'total_candidates' => 1,
                'safe_auto_apply_count' => 1,
                'manual_review_needed_count' => 0,
                'items' => array($row),
                'skipped' => array(),
            );
            $result = $this->aysa_technical_site_audit_apply_image_alt_package($project_id, $package);
            if (intval($result['applied_count'] ?? 0) > 0) {
                return array('applied' => true, 'operation' => 'site_audit_image_alt_apply', 'summary' => $result);
            }
            if (intval($result['manual_needed_count'] ?? 0) > 0) {
                return array('manual_needed' => true, 'reason' => 'image_alt_manual_needed', 'summary' => $result);
            }
            return array(
                'applied' => false,
                'reason' => sanitize_key((string) ($result['results'][0]['reason'] ?? 'image_alt_apply_failed')),
                'summary' => $result,
            );
        }
        return array('manual_needed' => true, 'reason' => 'manual_needed');
    }

    private function aysa_agent8v3_log_technical_fix_execution($plan, $result) {
        $events = get_option('aysa_agent8v3_technical_fix_executions', array());
        $events = is_array($events) ? $events : array();
        array_unshift($events, array(
            'action' => 'execute_technical_fix_plan',
            'module' => sanitize_key((string) ($result['module'] ?? $plan['module'] ?? '')),
            'state_version' => intval($plan['state_version'] ?? 0),
            'accepted_count' => intval($result['accepted_count'] ?? 0),
            'applied_count' => intval($result['applied_count'] ?? 0),
            'failed_count' => intval($result['failed_count'] ?? 0),
            'manual_needed_count' => intval($result['manual_needed_count'] ?? 0),
            'payload_hash' => sanitize_text_field((string) ($result['payload_hash'] ?? $plan['payload_hash'] ?? '')),
            'status' => sanitize_key((string) ($result['status'] ?? 'unknown')),
            'error' => isset($result['error']) ? sanitize_text_field((string) $result['error']) : null,
            'timestamp' => current_time('mysql'),
        ));
        update_option('aysa_agent8v3_technical_fix_executions', array_slice($events, 0, 50), false);
    }

    public function handle_agent8v3_image_alt_test_prepare() {
        if (!$this->aysa_agent8v3_image_alt_test_authorized()) {
            wp_send_json_error(array('message' => 'Unauthorized'), 403);
        }

        $max_items = 3;
        $batch = $this->aysa_agent8v3_prepare_image_alt_test_batch($max_items);
        $test_run_id = 'image_alt_test_' . get_current_user_id() . '_' . time() . '_' . wp_generate_password(8, false, false);
        $token = wp_generate_password(32, false, false);
        $now = time();
        $mode = array(
            'enabled' => true,
            'enabled_by' => get_current_user_id(),
            'enabled_at' => $now,
            'expires_at' => $now + 30 * MINUTE_IN_SECONDS,
            'max_items' => $max_items,
            'target_module' => 'image_alt',
            'billing_status' => 'test_mode_real_billing',
            'billing_unit_cents' => 5,
            'billing_policy' => 'verified_changed_images_only_cost_x5',
            'test_run_id' => $test_run_id,
            'one_time_token' => $token,
            'selected_items' => $batch,
        );
        update_option('aysa_agent8v3_image_alt_test_mode', $mode, false);
        $this->aysa_agent8v3_log_image_alt_test_event('enabled', array(
            'test_run_id' => $test_run_id,
            'attachment_ids' => array_map('intval', wp_list_pluck($batch, 'attachment_id')),
            'count' => count($batch),
        ));

        $project_id = intval(get_option('aysa_project_id', 0));
        $billing_identity = $this->aysa_agent8v3_image_alt_test_billing_identity($project_id);
        $wallet_snapshot = $this->aysa_agent8v3_image_alt_test_wallet_snapshot($project_id);
        $identity_health = $this->aysa_agent8v3_image_alt_billing_identity_health($project_id);

        wp_send_json_success(array(
            'test_mode' => $this->aysa_agent8v3_public_image_alt_test_mode($mode),
            'items' => $batch,
            'history' => $this->aysa_agent8v3_public_image_alt_history(),
            'billing_status' => 'test_mode_real_billing',
            'billing_unit_cents' => 5,
            'billing_identity' => $billing_identity,
            'billing_identity_health' => $identity_health,
            'wallet_snapshot' => $wallet_snapshot,
        ));
    }

    public function handle_agent8v3_image_alt_test_run() {
        if (!$this->aysa_agent8v3_image_alt_test_authorized()) {
            wp_send_json_error(array('message' => 'Unauthorized'), 403);
        }

        $mode = $this->aysa_agent8v3_current_image_alt_test_mode();
        if (empty($mode['enabled'])) {
            wp_send_json_error(array('message' => 'Image Alt test mode is not enabled.'), 400);
        }
        if ($this->aysa_agent8v3_image_alt_test_expired($mode)) {
            $this->aysa_agent8v3_disable_image_alt_test_mode('expired');
            wp_send_json_error(array('message' => 'Image Alt test mode expired.'), 400);
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $selected_ids = isset($_POST['selected_ids']) ? (array) wp_unslash($_POST['selected_ids']) : array();
        $selected_ids = array_values(array_unique(array_filter(array_map('intval', $selected_ids))));
        $max_items = max(1, min(3, intval($mode['max_items'] ?? 3)));
        if ($selected_ids === array()) {
            wp_send_json_error(array('message' => 'Select at least one image.'), 400);
        }
        if (count($selected_ids) > $max_items) {
            wp_send_json_error(array('message' => 'Image Alt test mode allows max 3 images.'), 400);
        }

        $prepared = is_array($mode['selected_items'] ?? null) ? $mode['selected_items'] : array();
        $selected_items = array();
        foreach ($prepared as $item) {
            $attachment_id = intval($item['attachment_id'] ?? 0);
            if ($attachment_id > 0 && in_array($attachment_id, $selected_ids, true)) {
                $current_alt = sanitize_text_field((string) get_post_meta($attachment_id, '_wp_attachment_image_alt', true));
                $selected_items[] = array(
                    'item_id' => sanitize_text_field((string) ($item['item_id'] ?? $attachment_id)),
                    'attachment_id' => $attachment_id,
                    'image_url' => esc_url_raw((string) ($item['image_url'] ?? '')),
                    'page_url' => '',
                    'old_alt' => $current_alt,
                    'proposed_alt' => sanitize_text_field((string) ($item['proposed_alt'] ?? '')),
                    'accepted_by_user' => true,
                );
            }
        }
        if (count($selected_items) !== count($selected_ids)) {
            wp_send_json_error(array('message' => 'Selected images must come from the prepared test batch.'), 400);
        }

        $unit_cost_cents = $this->aysa_agent8v3_image_alt_test_unit_cost_cents();
        $max_cost_cents = count($selected_items) * $unit_cost_cents;
        $test_run_id = sanitize_text_field((string) ($mode['test_run_id'] ?? 'image_alt_test'));
        $payload_hash = hash('sha256', wp_json_encode($selected_items));
        $billing_preflight = $this->aysa_agent8v3_image_alt_test_billing_preflight($max_cost_cents, $test_run_id);
        if (empty($billing_preflight['ok'])) {
            wp_send_json_error(array(
                'message' => sanitize_text_field((string) ($billing_preflight['message'] ?? 'Insufficient credits for Image Alt test.')),
                'billing' => $billing_preflight,
            ), 402);
        }
        $payload = array(
            'operation' => 'technical_image_alt_apply_bridge',
            'module' => 'image_alt',
            'selected_items' => $selected_items,
            'user_confirmed' => true,
            'confirmed_by_user' => true,
            'writes_enabled' => true,
            'dry_run' => false,
            'billing_status' => 'test_mode_real_billing',
            'billing_unit_cents' => $unit_cost_cents,
            'billing_cost_cents' => 0,
            'actual_credits_cents' => 0,
            'idempotency_key' => 'agent8v3:image_alt_test:' . $test_run_id . ':' . substr($payload_hash, 0, 16),
            'payload_hash' => $payload_hash,
            'test_mode' => true,
            'test_run_id' => $test_run_id,
            'test_mode_token' => sanitize_text_field((string) ($mode['one_time_token'] ?? '')),
        );
        $project_id = intval(get_option('aysa_project_id', 0));
        $result = $this->aysa_agent8v3_technical_image_alt_apply_bridge($project_id, $payload);
        $billing_result = $this->aysa_agent8v3_bill_image_alt_test_result($project_id, $payload, $result);
        $this->aysa_agent8v3_update_image_alt_history_billing(is_array($result['item_results'] ?? null) ? $result['item_results'] : array(), $billing_result);
        $result['billing'] = $billing_result;
        $result['billing_status'] = (string) ($billing_result['status'] ?? 'billing_not_applicable');
        $result['billing_cost_cents'] = intval($billing_result['cost_cents'] ?? 0);
        $result['actual_credits_cents'] = intval($billing_result['cost_cents'] ?? 0);

        $this->aysa_agent8v3_log_image_alt_test_event('run', array(
            'test_run_id' => $test_run_id,
            'attachment_ids' => $selected_ids,
            'status' => sanitize_key((string) ($result['status'] ?? 'unknown')),
            'verified_count' => intval($result['verified_count'] ?? 0),
            'failed_count' => intval($result['failed_count'] ?? 0),
            'billing_status' => sanitize_key((string) ($billing_result['status'] ?? 'unknown')),
            'billing_cost_cents' => intval($billing_result['cost_cents'] ?? 0),
            'item_results' => is_array($result['item_results'] ?? null) ? $result['item_results'] : array(),
        ));
        $this->aysa_agent8v3_disable_image_alt_test_mode('run_completed');

        wp_send_json_success(array(
            'result' => $result,
            'test_mode' => array('enabled' => false, 'disabled_reason' => 'run_completed'),
            'history' => $this->aysa_agent8v3_public_image_alt_history(),
            'billing_status' => 'test_mode_real_billing',
            'billing_unit_cents' => 5,
        ));
    }

    public function handle_agent8v3_image_alt_test_disable() {
        if (!$this->aysa_agent8v3_image_alt_test_authorized()) {
            wp_send_json_error(array('message' => 'Unauthorized'), 403);
        }
        $this->aysa_agent8v3_disable_image_alt_test_mode('manual_disable');
        wp_send_json_success(array(
            'test_mode' => array('enabled' => false, 'disabled_reason' => 'manual_disable'),
            'history' => $this->aysa_agent8v3_public_image_alt_history(),
            'billing_status' => 'test_mode_real_billing',
            'billing_unit_cents' => 5,
        ));
    }

    public function handle_agent8v3_image_alt_test_history() {
        if (!$this->aysa_agent8v3_image_alt_test_authorized()) {
            wp_send_json_error(array('message' => 'Unauthorized'), 403);
        }
        wp_send_json_success(array(
            'history' => $this->aysa_agent8v3_public_image_alt_history(),
            'billing_status' => 'test_mode_real_billing',
            'restore_billing_status' => 'not_billable_restore',
            'restore_billing_cost_cents' => 0,
            'billing_identity' => $this->aysa_agent8v3_image_alt_test_billing_identity(intval(get_option('aysa_project_id', 0))),
            'billing_identity_health' => $this->aysa_agent8v3_image_alt_billing_identity_health(intval(get_option('aysa_project_id', 0))),
            'wallet_snapshot' => $this->aysa_agent8v3_image_alt_test_wallet_snapshot(intval(get_option('aysa_project_id', 0))),
            'product_gates' => $this->aysa_agent8v3_image_alt_product_gates(),
        ));
    }

    public function handle_agent8v3_image_alt_product_gates_set() {
        if (!$this->aysa_agent8v3_image_alt_test_authorized()) {
            wp_send_json_error(array('message' => 'Unauthorized'), 403);
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_gate = !empty($_POST['project_gate_enabled']) && sanitize_text_field((string) wp_unslash($_POST['project_gate_enabled'])) !== '0';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $site_gate = !empty($_POST['site_gate_enabled']) && sanitize_text_field((string) wp_unslash($_POST['site_gate_enabled'])) !== '0';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $temporary_ttl = max(0, min(3600, intval(wp_unslash($_POST['temporary_ttl_seconds'] ?? 0))));

        update_option('aysa_agent8v3_image_alt_project_gate_enabled', $project_gate ? '1' : '0', false);
        update_option('aysa_agent8v3_image_alt_site_gate_enabled', $site_gate ? '1' : '0', false);
        update_option(
            'aysa_agent8v3_image_alt_gate_expires_at',
            ($project_gate || $site_gate) && $temporary_ttl > 0 ? time() + $temporary_ttl : 0,
            false
        );
        $this->aysa_agent8v3_log_image_alt_test_event('product_gates_set', array(
            'project_gate_enabled' => $project_gate,
            'site_gate_enabled' => $site_gate,
        ));

        wp_send_json_success(array(
            'product_gates' => $this->aysa_agent8v3_image_alt_product_gates(),
            'billing_identity' => $this->aysa_agent8v3_image_alt_test_billing_identity(intval(get_option('aysa_project_id', 0))),
            'billing_identity_health' => $this->aysa_agent8v3_image_alt_billing_identity_health(intval(get_option('aysa_project_id', 0))),
            'wallet_snapshot' => $this->aysa_agent8v3_image_alt_test_wallet_snapshot(intval(get_option('aysa_project_id', 0))),
        ));
    }

    public function handle_agent8v3_image_alt_billing_identity_repair() {
        if (!$this->aysa_agent8v3_image_alt_test_authorized()) {
            wp_send_json_error(array('message' => 'Unauthorized'), 403);
        }

        $project_id = intval(get_option('aysa_project_id', 0));
        $before = $this->aysa_agent8v3_image_alt_billing_identity_health($project_id);
        $repair = $this->aysa_agent8v3_repair_image_alt_billing_identity();
        $after = $this->aysa_agent8v3_image_alt_billing_identity_health($project_id > 0 ? $project_id : intval(get_option('aysa_project_id', 0)));
        $ok = !empty($repair['ok']) && !empty($after['remote_identity_present']);

        $payload = array(
            'ok' => $ok,
            'repair' => $repair,
            'before' => $before,
            'billing_identity' => $this->aysa_agent8v3_image_alt_test_billing_identity(intval($after['project_id'] ?? $project_id)),
            'billing_identity_health' => $after,
            'wallet_snapshot' => $this->aysa_agent8v3_image_alt_test_wallet_snapshot(intval($after['project_id'] ?? $project_id)),
            'message' => $ok
                ? 'AYSA billing identity repaired.'
                : 'AYSA billing identity could not be repaired safely.',
        );

        if ($ok) {
            wp_send_json_success($payload);
            return;
        }

        wp_send_json_error($payload, 400);
    }

    public function handle_agent8v3_image_alt_test_restore() {
        if (!$this->aysa_agent8v3_image_alt_test_authorized()) {
            wp_send_json_error(array('message' => 'Unauthorized'), 403);
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $history_id = sanitize_text_field((string) wp_unslash($_POST['history_id'] ?? ''));
        if ($history_id === '') {
            wp_send_json_error(array('message' => 'Missing history id.'), 400);
        }
        $result = $this->aysa_agent8v3_restore_image_alt_history_row($history_id);
        wp_send_json_success(array(
            'result' => $result,
            'history' => $this->aysa_agent8v3_public_image_alt_history(),
            'billing_status' => 'not_billable_restore',
            'billing_cost_cents' => 0,
        ));
    }

    private function aysa_agent8v3_image_alt_test_authorized() {
        if (!is_user_logged_in() || !current_user_can('manage_options')) {
            return false;
        }
        return check_ajax_referer('aysa_nonce', 'nonce', false) !== false;
    }

    private function aysa_agent8v3_image_alt_product_gates() {
        $project_gate = get_option('aysa_agent8v3_image_alt_project_gate_enabled', '0') === '1';
        $site_gate = get_option('aysa_agent8v3_image_alt_site_gate_enabled', '0') === '1';
        $expires_at = intval(get_option('aysa_agent8v3_image_alt_gate_expires_at', 0));
        if ($expires_at > 0 && time() >= $expires_at) {
            $project_gate = false;
            $site_gate = false;
        }
        $kill_switch = $this->aysa_agent8v3_env_flag('AYSA_AGENT8V3_TECHNICAL_APPLY_KILL_SWITCH')
            || $this->aysa_agent8v3_env_flag('AYSA_AGENT8V3_TECHNICAL_APPLY_IMAGE_ALT_KILL_SWITCH');
        return array(
            'module' => 'image_alt',
            'project_gate_enabled' => $project_gate,
            'api_project_gate_enabled' => $project_gate,
            'site_gate_enabled' => $site_gate,
            'plugin_site_gate_enabled' => $site_gate,
            'global_kill_switch_active' => $kill_switch,
            'normal_flow_enabled' => $project_gate && $site_gate && !$kill_switch,
            'default_state' => 'off',
            'source' => 'plugin_admin_options',
            'temporary_expires_at' => $expires_at,
            'legacy_env_apply_enabled' => $this->aysa_agent8v3_env_flag('AYSA_AGENT8V3_TECHNICAL_APPLY_ENABLED'),
            'legacy_env_image_alt_enabled' => $this->aysa_agent8v3_env_flag('AYSA_AGENT8V3_TECHNICAL_APPLY_IMAGE_ALT_ENABLED'),
            'legacy_env_image_alt_writes_enabled' => $this->aysa_agent8v3_env_flag('AYSA_AGENT8V3_TECHNICAL_APPLY_IMAGE_ALT_WRITES_ENABLED'),
            'legacy_env_image_alt_verify_enabled' => $this->aysa_agent8v3_env_flag('AYSA_AGENT8V3_TECHNICAL_APPLY_IMAGE_ALT_VERIFY_ENABLED'),
            'legacy_env_image_alt_agent8_flow_enabled' => $this->aysa_agent8v3_env_flag('AYSA_AGENT8V3_TECHNICAL_APPLY_IMAGE_ALT_AGENT8_FLOW_ENABLED'),
        );
    }

    private function aysa_agent8v3_restore_temporary_image_alt_product_gates($payload) {
        $payload = is_array($payload) ? $payload : array();
        if (empty($payload['temporary_product_gates'])) {
            return false;
        }
        update_option('aysa_agent8v3_image_alt_project_gate_enabled', '0', false);
        update_option('aysa_agent8v3_image_alt_site_gate_enabled', '0', false);
        update_option('aysa_agent8v3_image_alt_gate_expires_at', 0, false);
        $this->aysa_agent8v3_log_image_alt_test_event('temporary_product_gates_restored', array(
            'idempotency_key' => sanitize_text_field((string) ($payload['idempotency_key'] ?? '')),
        ));
        return true;
    }

    private function aysa_agent8v3_current_image_alt_test_mode() {
        $mode = get_option('aysa_agent8v3_image_alt_test_mode', array());
        return is_array($mode) ? $mode : array();
    }

    private function aysa_agent8v3_public_image_alt_test_mode($mode) {
        $mode = is_array($mode) ? $mode : array();
        return array(
            'enabled' => !empty($mode['enabled']),
            'enabled_by' => intval($mode['enabled_by'] ?? 0),
            'enabled_at' => intval($mode['enabled_at'] ?? 0),
            'expires_at' => intval($mode['expires_at'] ?? 0),
            'max_items' => intval($mode['max_items'] ?? 3),
            'target_module' => 'image_alt',
            'billing_status' => 'test_mode_real_billing',
            'billing_unit_cents' => $this->aysa_agent8v3_image_alt_test_unit_cost_cents(),
            'test_run_id' => sanitize_text_field((string) ($mode['test_run_id'] ?? '')),
        );
    }

    private function aysa_agent8v3_image_alt_test_expired($mode) {
        $expires_at = intval(is_array($mode) ? ($mode['expires_at'] ?? 0) : 0);
        return $expires_at <= 0 || time() > $expires_at;
    }

    private function aysa_agent8v3_disable_image_alt_test_mode($reason) {
        $mode = $this->aysa_agent8v3_current_image_alt_test_mode();
        $this->aysa_agent8v3_log_image_alt_test_event('disabled', array(
            'test_run_id' => sanitize_text_field((string) ($mode['test_run_id'] ?? '')),
            'reason' => sanitize_key((string) $reason),
        ));
        update_option('aysa_agent8v3_image_alt_test_mode', array(
            'enabled' => false,
            'disabled_at' => time(),
            'disabled_by' => get_current_user_id(),
            'disabled_reason' => sanitize_key((string) $reason),
            'target_module' => 'image_alt',
            'billing_status' => 'test_mode_real_billing',
        ), false);
    }

    private function aysa_agent8v3_log_image_alt_test_event($event, $data = array()) {
        $log = get_option('aysa_agent8v3_image_alt_test_log', array());
        $log = is_array($log) ? $log : array();
        $log[] = array(
            'event' => sanitize_key((string) $event),
            'timestamp' => current_time('mysql'),
            'user_id' => get_current_user_id(),
            'data' => $this->aysa_agent8v3_sanitize_image_alt_test_log_data(is_array($data) ? $data : array()),
        );
        update_option('aysa_agent8v3_image_alt_test_log', array_slice($log, -50), false);
    }

    private function aysa_agent8v3_sanitize_image_alt_test_log_data($data) {
        $out = array();
        foreach ($data as $key => $value) {
            $safe_key = sanitize_key((string) $key);
            if ($safe_key === '') {
                continue;
            }
            if (is_array($value)) {
                $out[$safe_key] = array_slice($this->aysa_agent8v3_sanitize_image_alt_test_log_data($value), 0, 20, true);
            } elseif (is_bool($value)) {
                $out[$safe_key] = $value;
            } elseif (is_numeric($value)) {
                $out[$safe_key] = $value + 0;
            } else {
                $out[$safe_key] = sanitize_text_field((string) $value);
            }
        }
        return $out;
    }

    private function aysa_agent8v3_image_alt_history_rows() {
        $rows = get_option('aysa_agent8v3_image_alt_history', array());
        return is_array($rows) ? array_values(array_filter($rows, 'is_array')) : array();
    }

    private function aysa_agent8v3_save_image_alt_history_rows($rows) {
        $rows = is_array($rows) ? array_values(array_filter($rows, 'is_array')) : array();
        update_option('aysa_agent8v3_image_alt_history', array_slice($rows, -100), false);
    }

    private function aysa_agent8v3_public_image_alt_history() {
        $rows = array_reverse($this->aysa_agent8v3_image_alt_history_rows());
        $public = array();
        foreach ($rows as $row) {
            if (!$this->aysa_agent8v3_image_alt_history_row_matches_current_scope($row)) {
                continue;
            }
            $attachment_id = intval($row['attachment_id'] ?? 0);
            $public[] = array(
                'history_id' => sanitize_text_field((string) ($row['history_id'] ?? '')),
                'attachment_id' => $attachment_id,
                'image_url' => esc_url_raw((string) ($row['image_url'] ?? '')),
                'filename' => sanitize_file_name((string) ($row['filename'] ?? '')),
                'old_alt' => sanitize_text_field((string) ($row['old_alt'] ?? '')),
                'new_alt' => sanitize_text_field((string) ($row['new_alt'] ?? '')),
                'final_alt' => sanitize_text_field((string) ($row['final_alt'] ?? '')),
                'status' => sanitize_key((string) ($row['status'] ?? 'unknown')),
                'verification_status' => sanitize_key((string) ($row['verification_status'] ?? 'not_verified')),
                'billing_status' => sanitize_key((string) ($row['billing_status'] ?? 'not_billed')),
                'billing_cost_cents' => intval($row['billing_cost_cents'] ?? 0),
                'restore_billing_status' => sanitize_key((string) ($row['restore_billing_status'] ?? '')),
                'restore_billing_cost_cents' => intval($row['restore_billing_cost_cents'] ?? 0),
                'timestamp' => sanitize_text_field((string) ($row['timestamp'] ?? '')),
                'updated_at' => sanitize_text_field((string) ($row['updated_at'] ?? '')),
                'restored_at' => sanitize_text_field((string) ($row['restored_at'] ?? '')),
                'error_code' => sanitize_key((string) ($row['error_code'] ?? '')),
                'restore_available' => $this->aysa_agent8v3_image_alt_history_restore_available($row),
            );
            if (count($public) >= 30) {
                break;
            }
        }
        return $public;
    }

    private function aysa_agent8v3_image_alt_history_row_matches_current_scope($row) {
        $row = is_array($row) ? $row : array();
        $current_project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
        $row_project_id = intval($row['project_id'] ?? 0);
        $current_site_host = strtolower(trim((string) $this->aysa_agent8v3_site_host()));
        $row_site_host = strtolower(trim((string) ($row['site_host'] ?? '')));
        if ($current_project_id <= 0 || $row_project_id <= 0 || $row_project_id !== $current_project_id) {
            return false;
        }
        if ($current_site_host === '' || $row_site_host === '') {
            return false;
        }
        return rtrim($row_site_host, '.') === rtrim($current_site_host, '.');
    }

    private function aysa_agent8v3_image_alt_history_restore_available($row) {
        $row = is_array($row) ? $row : array();
        $status = sanitize_key((string) ($row['status'] ?? ''));
        $attachment_id = intval($row['attachment_id'] ?? 0);
        return $attachment_id > 0
            && in_array($status, array('changed', 'verified'), true)
            && empty($row['restored_at'])
            && array_key_exists('old_alt', $row);
    }

    private function aysa_agent8v3_create_image_alt_history_row($item, $context, $current_alt) {
        $item = is_array($item) ? $item : array();
        $context = is_array($context) ? $context : array();
        $attachment_id = intval($item['attachment_id'] ?? $item['media_id'] ?? 0);
        $history_id = 'iah_' . time() . '_' . wp_generate_password(8, false, false);
        $row = array(
            'source' => 'agent8v3_image_alt_controlled_beta',
            'history_id' => $history_id,
            'attachment_id' => $attachment_id,
            'image_url' => esc_url_raw((string) ($item['image_url'] ?? wp_get_attachment_url($attachment_id))),
            'filename' => sanitize_file_name((string) basename((string) get_attached_file($attachment_id))),
            'old_alt' => sanitize_text_field((string) $current_alt),
            'new_alt' => sanitize_text_field((string) ($item['proposed_alt'] ?? '')),
            'final_alt' => '',
            'user_id' => get_current_user_id(),
            'project_id' => intval($context['project_id'] ?? 0),
            'site_host' => sanitize_text_field((string) ($context['site_host'] ?? $this->aysa_agent8v3_site_host())),
            'test_run_id' => sanitize_text_field((string) ($context['test_run_id'] ?? '')),
            'idempotency_key' => sanitize_text_field((string) ($context['idempotency_key'] ?? '')),
            'timestamp' => current_time('mysql'),
            'updated_at' => current_time('mysql'),
            'billing_status' => 'pending_result',
            'billing_cost_cents' => 0,
            'verification_status' => 'pending',
            'status' => 'submitted',
            'error_code' => '',
        );
        $rows = $this->aysa_agent8v3_image_alt_history_rows();
        $rows[] = $row;
        $this->aysa_agent8v3_save_image_alt_history_rows($rows);
        $this->aysa_agent8v3_log_image_alt_test_event('history_created', array(
            'history_id' => $history_id,
            'attachment_id' => $attachment_id,
            'test_run_id' => $row['test_run_id'],
        ));
        return $history_id;
    }

    private function aysa_agent8v3_update_image_alt_history_row($history_id, $updates) {
        $history_id = sanitize_text_field((string) $history_id);
        if ($history_id === '') {
            return;
        }
        $rows = $this->aysa_agent8v3_image_alt_history_rows();
        foreach ($rows as $index => $row) {
            if (sanitize_text_field((string) ($row['history_id'] ?? '')) !== $history_id) {
                continue;
            }
            foreach ((array) $updates as $key => $value) {
                $safe_key = sanitize_key((string) $key);
                if ($safe_key === '') {
                    continue;
                }
                if (in_array($safe_key, array('old_alt', 'new_alt', 'final_alt', 'restored_alt', 'current_alt_before_restore'), true)) {
                    $row[$safe_key] = sanitize_text_field((string) $value);
                } elseif (in_array($safe_key, array('image_url'), true)) {
                    $row[$safe_key] = esc_url_raw((string) $value);
                } elseif (in_array($safe_key, array('billing_cost_cents', 'attachment_id', 'project_id', 'user_id'), true)) {
                    $row[$safe_key] = intval($value);
                } else {
                    $row[$safe_key] = sanitize_text_field((string) $value);
                }
            }
            $row['updated_at'] = current_time('mysql');
            $rows[$index] = $row;
            $this->aysa_agent8v3_save_image_alt_history_rows($rows);
            return;
        }
    }

    private function aysa_agent8v3_image_alt_history_status_from_item_result($item_result) {
        $status = sanitize_key((string) ($item_result['status'] ?? 'failed'));
        if ($status === 'verified' && !empty($item_result['changed'])) {
            return 'changed';
        }
        if ($status === 'skipped_no_change') {
            return 'no_change';
        }
        if (str_starts_with($status, 'skipped')) {
            return 'skipped';
        }
        if ($status === 'manual_review_needed') {
            return 'skipped';
        }
        return 'failed';
    }

    private function aysa_agent8v3_update_image_alt_history_billing($item_results, $billing_result) {
        $item_results = is_array($item_results) ? $item_results : array();
        $billing_result = is_array($billing_result) ? $billing_result : array();
        $unit_cost_cents = intval($billing_result['unit_cost_cents'] ?? $this->aysa_agent8v3_image_alt_test_unit_cost_cents());
        $billing_status = sanitize_key((string) ($billing_result['status'] ?? 'not_billed'));
        foreach ($item_results as $item) {
            if (!is_array($item)) {
                continue;
            }
            $history_id = sanitize_text_field((string) ($item['history_id'] ?? ''));
            if ($history_id === '') {
                continue;
            }
            $charged = (string) ($item['status'] ?? '') === 'verified' && !empty($item['changed']) && intval($billing_result['cost_cents'] ?? 0) > 0;
            $this->aysa_agent8v3_update_image_alt_history_row($history_id, array(
                'billing_status' => $charged ? $billing_status : 'not_billable_no_verified_change',
                'billing_cost_cents' => $charged ? $unit_cost_cents : 0,
                'billing_reference' => sanitize_text_field((string) ($billing_result['reference'] ?? '')),
            ));
        }
    }

    private function aysa_agent8v3_restore_image_alt_history_row($history_id) {
        $history_id = sanitize_text_field((string) $history_id);
        $rows = $this->aysa_agent8v3_image_alt_history_rows();
        $target_index = null;
        $target = null;
        foreach ($rows as $index => $row) {
            if (sanitize_text_field((string) ($row['history_id'] ?? '')) === $history_id) {
                $target_index = $index;
                $target = $row;
                break;
            }
        }
        $base = array(
            'history_id' => $history_id,
            'attachment_id' => intval(is_array($target) ? ($target['attachment_id'] ?? 0) : 0),
            'old_alt' => sanitize_text_field((string) (is_array($target) ? ($target['old_alt'] ?? '') : '')),
            'current_alt_before_restore' => '',
            'restored_alt' => '',
            'status' => 'failed',
            'verification_status' => 'not_verified',
            'error_code' => '',
            'changed' => false,
            'billing_status' => 'not_billable_restore',
            'billing_cost_cents' => 0,
        );
        $this->aysa_agent8v3_log_image_alt_test_event('restore_attempted', array('history_id' => $history_id));
        if ($target_index === null || !is_array($target)) {
            return array_merge($base, array('status' => 'failed', 'error_code' => 'history_row_missing'));
        }
        if (!$this->aysa_agent8v3_image_alt_history_row_matches_current_scope($target)) {
            return array_merge($base, array('status' => 'failed', 'error_code' => 'history_row_scope_mismatch'));
        }
        if (sanitize_text_field((string) ($target['source'] ?? '')) !== 'agent8v3_image_alt_controlled_beta') {
            return array_merge($base, array('status' => 'failed', 'error_code' => 'history_row_not_image_alt_controlled_beta'));
        }
        if (!empty($target['restored_at']) || sanitize_key((string) ($target['status'] ?? '')) === 'restored') {
            return array_merge($base, array(
                'status' => 'skipped_already_restored',
                'verification_status' => 'verified',
                'restored_alt' => sanitize_text_field((string) ($target['final_alt'] ?? $target['old_alt'] ?? '')),
            ));
        }
        if (!$this->aysa_agent8v3_image_alt_history_restore_available($target)) {
            return array_merge($base, array('status' => 'failed', 'error_code' => 'history_row_not_restore_eligible'));
        }
        $attachment_id = intval($target['attachment_id'] ?? 0);
        if ($attachment_id <= 0 || get_post_type($attachment_id) !== 'attachment') {
            $this->aysa_agent8v3_update_image_alt_history_row($history_id, array(
                'status' => 'failed',
                'error_code' => 'attachment_missing',
                'verification_status' => 'failed',
            ));
            return array_merge($base, array('status' => 'skipped_missing_attachment', 'error_code' => 'attachment_missing'));
        }
        $old_alt = sanitize_text_field((string) ($target['old_alt'] ?? ''));
        $expected_new_alt = sanitize_text_field((string) ($target['final_alt'] ?: ($target['new_alt'] ?? '')));
        $current_alt = sanitize_text_field((string) get_post_meta($attachment_id, '_wp_attachment_image_alt', true));
        $base['current_alt_before_restore'] = $current_alt;
        if ($current_alt === $old_alt) {
            $this->aysa_agent8v3_update_image_alt_history_row($history_id, array(
                'status' => 'restored',
                'restored_at' => current_time('mysql'),
                'restored_alt' => $old_alt,
                'final_alt' => $old_alt,
                'verification_status' => 'verified',
                'restore_billing_status' => 'not_billable_restore',
                'restore_billing_cost_cents' => 0,
            ));
            $this->aysa_agent8v3_log_image_alt_test_event('restore_verified', array(
                'history_id' => $history_id,
                'attachment_id' => $attachment_id,
                'changed' => false,
            ));
            return array_merge($base, array('status' => 'restored', 'verification_status' => 'verified', 'restored_alt' => $old_alt));
        }
        if ($expected_new_alt !== '' && $current_alt !== $expected_new_alt) {
            $this->aysa_agent8v3_update_image_alt_history_row($history_id, array(
                'error_code' => 'concurrent_alt_changed',
                'verification_status' => 'manual_review_needed',
            ));
            $this->aysa_agent8v3_log_image_alt_test_event('restore_skipped', array(
                'history_id' => $history_id,
                'attachment_id' => $attachment_id,
                'error_code' => 'concurrent_alt_changed',
            ));
            return array_merge($base, array(
                'status' => 'manual_review_needed',
                'verification_status' => 'manual_review_needed',
                'error_code' => 'concurrent_alt_changed',
            ));
        }

        update_post_meta($attachment_id, '_wp_attachment_image_alt', $old_alt);
        $readback = sanitize_text_field((string) get_post_meta($attachment_id, '_wp_attachment_image_alt', true));
        if ($readback === $old_alt) {
            $this->aysa_agent8v3_update_image_alt_history_row($history_id, array(
                'status' => 'restored',
                'restored_at' => current_time('mysql'),
                'restored_by' => get_current_user_id(),
                'restored_alt' => $readback,
                'final_alt' => $readback,
                'verification_status' => 'verified',
                'restore_billing_status' => 'not_billable_restore',
                'restore_billing_cost_cents' => 0,
            ));
            $this->aysa_agent8v3_log_image_alt_test_event('restore_verified', array(
                'history_id' => $history_id,
                'attachment_id' => $attachment_id,
                'changed' => true,
            ));
            return array_merge($base, array(
                'status' => 'restored',
                'verification_status' => 'verified',
                'restored_alt' => $readback,
                'changed' => true,
            ));
        }
        $this->aysa_agent8v3_update_image_alt_history_row($history_id, array(
            'status' => 'failed',
            'error_code' => 'restore_verification_mismatch',
            'verification_status' => 'failed',
            'final_alt' => $readback,
        ));
        $this->aysa_agent8v3_log_image_alt_test_event('restore_failed', array(
            'history_id' => $history_id,
            'attachment_id' => $attachment_id,
            'error_code' => 'restore_verification_mismatch',
        ));
        return array_merge($base, array(
            'status' => 'failed',
            'verification_status' => 'failed',
            'restored_alt' => $readback,
            'error_code' => 'restore_verification_mismatch',
            'changed' => true,
        ));
    }

    private function aysa_agent8v3_prepare_image_alt_test_batch($max_items = 3) {
        $posts = get_posts(array(
            'post_type' => 'attachment',
            'post_status' => 'inherit',
            'post_mime_type' => 'image',
            'posts_per_page' => 80,
            'orderby' => 'rand',
            'fields' => 'ids',
        ));
        $items = array();
        foreach ($posts as $attachment_id) {
            $attachment_id = intval($attachment_id);
            if ($attachment_id <= 0 || !$this->aysa_agent8v3_image_alt_test_attachment_allowed($attachment_id)) {
                continue;
            }
            $old_alt = sanitize_text_field((string) get_post_meta($attachment_id, '_wp_attachment_image_alt', true));
            if (!$this->aysa_agent8v3_image_alt_test_weak_alt($old_alt)) {
                continue;
            }
            $file = basename((string) get_attached_file($attachment_id));
            $title = get_the_title($attachment_id);
            $image_url = wp_get_attachment_url($attachment_id);
            $items[] = array(
                'item_id' => 'test_' . $attachment_id,
                'attachment_id' => $attachment_id,
                'image_url' => esc_url_raw((string) $image_url),
                'filename' => sanitize_file_name($file),
                'title' => sanitize_text_field((string) $title),
                'old_alt' => $old_alt,
                'proposed_alt' => $this->aysa_agent8v3_propose_image_alt_test_text($attachment_id, $file, $title),
                'accepted_by_user' => true,
            );
            if (count($items) >= max(1, min(3, intval($max_items)))) {
                break;
            }
        }
        return $items;
    }

    private function aysa_agent8v3_image_alt_test_attachment_allowed($attachment_id) {
        $meta = wp_get_attachment_metadata($attachment_id);
        $width = intval($meta['width'] ?? 0);
        $height = intval($meta['height'] ?? 0);
        if ($width < 300 || $height < 200) {
            return false;
        }
        $file = strtolower((string) basename((string) get_attached_file($attachment_id)));
        $title = strtolower((string) get_the_title($attachment_id));
        $pattern = '/logo|favicon|icon|badge|sprite|avatar|placeholder|tracking|pixel|loader|spinner|payment|visa|mastercard|facebook|instagram|linkedin|whatsapp|svg|cropped|brand|marca|watermark|mobile|desktop|screenshot|screen|ui|app|banner|header|footer|android|chrome|apple-touch|512x512|192x192/i';
        return !preg_match($pattern, $file . ' ' . $title);
    }

    private function aysa_agent8v3_image_alt_test_weak_alt($alt) {
        $alt = strtolower(trim((string) $alt));
        if ($alt === '' || strlen($alt) < 12) {
            return true;
        }
        return in_array($alt, array('image', 'img', 'poza', 'foto', 'photo', 'picture', 'placeholder', 'untitled', 'imagine'), true);
    }

    private function aysa_agent8v3_propose_image_alt_test_text($attachment_id, $file, $title) {
        $source = trim((string) $title);
        if ($source === '') {
            $source = preg_replace('/\.[a-z0-9]+$/i', '', (string) $file);
        }
        $source = preg_replace('/[-_]+/', ' ', $source);
        $source = trim(preg_replace('/\s+/', ' ', (string) $source));
        if ($source === '') {
            return 'Descriere imagine SEO';
        }
        $prefix = preg_match('/masina|auto|inchir/i', $source) ? 'Masina de inchiriat - ' : 'Imagine - ';
        $alt = $prefix . $source;
        return sanitize_text_field(strlen($alt) > 125 ? substr($alt, 0, 122) . '...' : $alt);
    }

    private function aysa_agent8v3_image_alt_test_mode_allows_bridge($payload, $selected_count) {
        if (empty($payload['test_mode']) || !is_user_logged_in() || !current_user_can('manage_options')) {
            return false;
        }
        $mode = $this->aysa_agent8v3_current_image_alt_test_mode();
        if (empty($mode['enabled']) || $this->aysa_agent8v3_image_alt_test_expired($mode)) {
            return false;
        }
        $token = sanitize_text_field((string) ($payload['test_mode_token'] ?? ''));
        if ($token === '' || !hash_equals((string) ($mode['one_time_token'] ?? ''), $token)) {
            return false;
        }
        $max_items = max(1, min(3, intval($mode['max_items'] ?? 3)));
        return sanitize_key((string) ($payload['module'] ?? '')) === 'image_alt'
            && $selected_count > 0
            && $selected_count <= $max_items
            && !empty($payload['user_confirmed'])
            && !empty($payload['writes_enabled'])
            && empty($payload['dry_run'])
            && sanitize_key((string) ($payload['billing_status'] ?? '')) === 'test_mode_real_billing'
            && sanitize_key((string) ($mode['target_module'] ?? '')) === 'image_alt';
    }

    private function aysa_agent8v3_image_alt_test_unit_cost_cents() {
        return 5;
    }

    private function aysa_agent8v3_image_alt_test_wallet_manager() {
        if (!class_exists('Aysa_Wallet_Manager')) {
            $wallet_file = AYSA_PLUGIN_DIR . 'includes/class-aysa-wallet-manager.php';
            if (file_exists($wallet_file)) {
                require_once $wallet_file;
            }
        }
        return class_exists('Aysa_Wallet_Manager') ? Aysa_Wallet_Manager::get_instance() : null;
    }

    private function aysa_agent8v3_image_alt_billing_identity_health($project_id) {
        $project_id = intval($project_id);
        $wallet = $this->aysa_agent8v3_image_alt_test_wallet_manager();
        if (!$wallet || !method_exists($wallet, 'get_billing_identity_health')) {
            return array(
                'project_id' => $project_id,
                'website_id' => intval(get_option('aysa_website_id', 0)),
                'normalized_domain' => $this->aysa_agent8v3_site_host(),
                'current_wp_user_id' => get_current_user_id(),
                'aysa_user_id' => 0,
                'aysa_user_id_present' => false,
                'current_wp_user_auth_row_present' => false,
                'option_aysa_user_id_present' => intval(get_option('aysa_user_id', 0)) > 0,
                'remote_identity_present' => false,
                'auth_domain' => sanitize_text_field((string) get_option('aysa_auth_domain', '')),
                'identity_source' => 'wallet_manager_unavailable',
                'wallet_snapshot_available' => false,
                'spendable_credits' => 0,
                'diagnostic_code' => 'wallet_manager_unavailable',
                'uses_wp_admin_email' => false,
                'wp_admin_notification_suppressed' => true,
                'secret_exposed' => false,
            );
        }

        $health = $wallet->get_billing_identity_health($project_id);
        return is_array($health) ? $this->aysa_agent8v3_sanitize_billing_identity_health($health) : array(
            'project_id' => $project_id,
            'website_id' => intval(get_option('aysa_website_id', 0)),
            'normalized_domain' => $this->aysa_agent8v3_site_host(),
            'current_wp_user_id' => get_current_user_id(),
            'aysa_user_id' => 0,
            'aysa_user_id_present' => false,
            'current_wp_user_auth_row_present' => false,
            'option_aysa_user_id_present' => false,
            'remote_identity_present' => false,
            'auth_domain' => '',
            'identity_source' => 'invalid_wallet_health',
            'wallet_snapshot_available' => false,
            'spendable_credits' => 0,
            'diagnostic_code' => 'invalid_wallet_health',
            'uses_wp_admin_email' => false,
            'wp_admin_notification_suppressed' => true,
            'secret_exposed' => false,
        );
    }

    private function aysa_agent8v3_sanitize_billing_identity_health($health) {
        $health = is_array($health) ? $health : array();
        return array(
            'project_id' => intval($health['project_id'] ?? 0),
            'website_id' => intval($health['website_id'] ?? 0),
            'normalized_domain' => strtolower(preg_replace('/^www\./', '', sanitize_text_field((string) ($health['normalized_domain'] ?? '')))),
            'current_wp_user_id' => intval($health['current_wp_user_id'] ?? 0),
            'aysa_user_id' => intval($health['aysa_user_id'] ?? 0),
            'aysa_user_id_present' => !empty($health['aysa_user_id_present']),
            'current_wp_user_auth_row_present' => !empty($health['current_wp_user_auth_row_present']),
            'option_aysa_user_id_present' => !empty($health['option_aysa_user_id_present']),
            'remote_identity_present' => !empty($health['remote_identity_present']),
            'auth_domain' => strtolower(preg_replace('/^www\./', '', sanitize_text_field((string) ($health['auth_domain'] ?? '')))),
            'identity_source' => sanitize_key((string) ($health['identity_source'] ?? 'missing')),
            'wallet_snapshot_available' => !empty($health['wallet_snapshot_available']),
            'spendable_credits' => intval($health['spendable_credits'] ?? 0),
            'diagnostic_code' => sanitize_key((string) ($health['diagnostic_code'] ?? 'unknown')),
            'uses_wp_admin_email' => false,
            'wp_admin_notification_suppressed' => true,
            'secret_exposed' => false,
        );
    }

    private function aysa_agent8v3_repair_image_alt_billing_identity() {
        $site_host = strtolower(preg_replace('/^www\./', '', trim((string) $this->aysa_agent8v3_site_host())));
        $project_id = intval(get_option('aysa_project_id', 0));
        $website_id = intval(get_option('aysa_website_id', 0));
        $access_key = $this->aysa_get_effective_access_key('');

        if ($access_key === '') {
            return array('ok' => false, 'status' => 'blocked_missing_access_key');
        }

        $validation = $this->aysa_validate_test_access_key($access_key, $site_host);
        if (empty($validation['valid'])) {
            return array(
                'ok' => false,
                'status' => 'blocked_access_key_invalid',
                'code' => sanitize_key((string) ($validation['code'] ?? 'access_key_invalid')),
            );
        }

        $validated_project_id = intval($validation['project_id'] ?? 0);
        $validated_website_id = intval($validation['website_id'] ?? 0);
        $validated_host = strtolower(preg_replace('/^www\./', '', trim((string) ($validation['host'] ?? $validation['domain'] ?? ''))));

        if ($project_id > 0 && $validated_project_id > 0 && $project_id !== $validated_project_id) {
            return array('ok' => false, 'status' => 'blocked_project_mismatch');
        }
        if ($website_id > 0 && $validated_website_id > 0 && $website_id !== $validated_website_id) {
            return array('ok' => false, 'status' => 'blocked_website_mismatch');
        }
        if ($site_host !== '' && $validated_host !== '' && $site_host !== $validated_host) {
            return array('ok' => false, 'status' => 'blocked_domain_mismatch');
        }

        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }
        if (!class_exists('Aysa_Auth_Manager')) {
            return array('ok' => false, 'status' => 'auth_manager_unavailable');
        }

        $auth_manager = Aysa_Auth_Manager::get_instance();
        $auth_bootstrap = $auth_manager->verify_and_store_secret_key($access_key, $site_host);
        if (!is_array($auth_bootstrap) || empty($auth_bootstrap['success']) || intval($auth_bootstrap['aysa_user_id'] ?? 0) <= 0) {
            return array(
                'ok' => false,
                'status' => 'auth_manager_rejected_activation_key',
                'code' => sanitize_key((string) ($auth_bootstrap['error'] ?? 'auth_bootstrap_failed')),
            );
        }

        update_option($this->aysa_test_access_key_option_name(), $access_key, false);
        if ($validated_project_id > 0) {
            update_option('aysa_project_id', $validated_project_id, false);
        }
        if ($validated_website_id > 0) {
            update_option('aysa_website_id', $validated_website_id, false);
        }
        $this->aysa_store_projection_sync_secret_from_validation($validation);
        update_option('aysa_user_secret_key', $access_key, false);
        update_option('aysa_user_id', intval($auth_bootstrap['aysa_user_id'] ?? 0), false);
        update_option('aysa_auth_domain', $site_host !== '' ? $site_host : wp_parse_url(home_url(), PHP_URL_HOST), false);

        if (!empty($validation['plan_code'])) {
            update_option('aysa_wallet_default_plan', sanitize_key((string) $validation['plan_code']), false);
            if (class_exists('Aysa_Wallet_Manager')) {
                $resolved_project_id = $validated_project_id > 0 ? $validated_project_id : intval(get_option('aysa_project_id', 0));
                if ($resolved_project_id > 0) {
                    Aysa_Wallet_Manager::get_instance()->apply_plan_to_project(
                        $resolved_project_id,
                        sanitize_key((string) $validation['plan_code']),
                        false
                    );
                }
            }
        }

        return array(
            'ok' => true,
            'status' => 'repaired',
            'project_id' => $validated_project_id > 0 ? $validated_project_id : intval(get_option('aysa_project_id', 0)),
            'website_id' => $validated_website_id > 0 ? $validated_website_id : intval(get_option('aysa_website_id', 0)),
            'normalized_domain' => $site_host,
            'aysa_user_id_present' => true,
            'uses_wp_admin_email' => false,
            'wp_admin_notification_suppressed' => true,
        );
    }

    private function aysa_agent8v3_image_alt_test_billing_identity($project_id) {
        $project_id = intval($project_id);
        $wallet = $this->aysa_agent8v3_image_alt_test_wallet_manager();
        if (!$wallet || !method_exists($wallet, 'get_billing_identity_snapshot')) {
            return array(
                'project_id' => $project_id,
                'aysa_user_id' => 0,
                'remote_identity_present' => false,
                'source' => 'aysa_project_account',
                'uses_wp_admin_email' => false,
                'wp_admin_notification_suppressed' => true,
                'error' => 'wallet_identity_unavailable',
            );
        }
        $identity = $wallet->get_billing_identity_snapshot($project_id);
        return is_array($identity) ? $identity : array(
            'project_id' => $project_id,
            'aysa_user_id' => 0,
            'remote_identity_present' => false,
            'source' => 'aysa_project_account',
            'uses_wp_admin_email' => false,
            'wp_admin_notification_suppressed' => true,
            'error' => 'invalid_billing_identity',
        );
    }

    private function aysa_agent8v3_image_alt_test_wallet_snapshot($project_id) {
        $project_id = intval($project_id);
        $wallet = $this->aysa_agent8v3_image_alt_test_wallet_manager();
        if ($project_id <= 0 || !$wallet || !method_exists($wallet, 'get_wallet_snapshot')) {
            return array(
                'project_id' => $project_id,
                'balance_cents' => 0,
                'spendable_cents' => 0,
                'reserved_links_cents' => 0,
                'available' => false,
            );
        }
        $snapshot = $wallet->get_wallet_snapshot($project_id, array('allow_remote' => true));
        return array(
            'project_id' => intval($snapshot['project_id'] ?? $project_id),
            'balance_cents' => intval($snapshot['balance_cents'] ?? 0),
            'spendable_cents' => intval($snapshot['spendable_cents'] ?? 0),
            'reserved_links_cents' => intval($snapshot['reserved_links_cents'] ?? 0),
            'latest_usage_amount_cents' => intval($snapshot['latest_usage_amount_cents'] ?? 0),
            'latest_usage_action_key' => sanitize_key((string) ($snapshot['latest_usage_action_key'] ?? '')),
            'available' => true,
        );
    }

    private function aysa_agent8v3_image_alt_test_billing_preflight($max_cost_cents, $test_run_id = '', $context = 'test') {
        $max_cost_cents = max(0, intval($max_cost_cents));
        $context = sanitize_key((string) $context);
        $is_agent8_flow = $context === 'agent8_flow';
        if ($max_cost_cents <= 0) {
            return array('ok' => true, 'max_cost_cents' => 0, 'spendable_cents' => 0);
        }
        $project_id = intval(get_option('aysa_project_id', 0));
        $wallet = $this->aysa_agent8v3_image_alt_test_wallet_manager();
        if ($project_id <= 0 || !$wallet || !method_exists($wallet, 'get_wallet_snapshot')) {
            return array('ok' => false, 'message' => $is_agent8_flow ? 'Wallet unavailable for Image Alt billing.' : 'Wallet unavailable for Image Alt test billing.', 'max_cost_cents' => $max_cost_cents);
        }
        $billing_identity = $this->aysa_agent8v3_image_alt_test_billing_identity($project_id);
        if (empty($billing_identity['remote_identity_present'])) {
            return array(
                'ok' => false,
                'message' => 'AYSA billing identity is missing. Image Alt paid write was blocked before any site change.',
                'max_cost_cents' => $max_cost_cents,
                'billing_identity' => $billing_identity,
                'topup_status' => 'blocked',
                'topup_unavailable_reason' => 'billing_identity_missing',
            );
        }
        $snapshot = $wallet->get_wallet_snapshot($project_id, array('allow_remote' => true));
        $spendable = max(0, intval($snapshot['spendable_cents'] ?? $snapshot['balance_cents'] ?? 0));
        if ($spendable < $max_cost_cents) {
            $shortfall = max(0, $max_cost_cents - $spendable);
            $suggested_topup = $this->aysa_agent8v3_image_alt_test_topup_package_cents($shortfall);
            $topup = $this->aysa_agent8v3_image_alt_test_topup_order($project_id, $suggested_topup, $test_run_id, $context);
            return array(
                'ok' => false,
                'message' => $is_agent8_flow ? 'Insufficient credits for Image Alt apply.' : 'Insufficient credits for Image Alt test.',
                'max_cost_cents' => $max_cost_cents,
                'spendable_cents' => $spendable,
                'shortfall_cents' => $shortfall,
                'suggested_topup_cents' => $suggested_topup,
                'topup_order' => is_array($topup['order'] ?? null) ? $topup['order'] : null,
                'topup_status' => sanitize_key((string) ($topup['status'] ?? 'unavailable')),
                'topup_unavailable_reason' => sanitize_text_field((string) ($topup['error'] ?? '')),
                'billing_identity' => $billing_identity,
            );
        }
        return array(
            'ok' => true,
            'max_cost_cents' => $max_cost_cents,
            'spendable_cents' => $spendable,
            'billing_identity' => $billing_identity,
        );
    }

    private function aysa_agent8v3_image_alt_test_topup_package_cents($shortfall_cents) {
        $required = max(1, intval($shortfall_cents));
        foreach (array(1000, 2500, 5000) as $package) {
            if ($package >= $required) {
                return $package;
            }
        }
        return 5000;
    }

    private function aysa_agent8v3_image_alt_test_topup_order($project_id, $credits_cents, $test_run_id = '', $context = 'test') {
        $project_id = intval($project_id);
        $credits_cents = max(0, intval($credits_cents));
        $test_run_id = sanitize_text_field((string) $test_run_id);
        $context = sanitize_key((string) $context);
        $is_agent8_flow = $context === 'agent8_flow';
        if ($project_id <= 0 || $credits_cents <= 0) {
            return array('status' => 'unavailable', 'error' => 'invalid_topup_context');
        }
        $records = get_option('aysa_agent8v3_image_alt_test_topup_orders', array());
        $records = is_array($records) ? $records : array();
        $key = hash('sha256', 'image_alt_' . $context . '_topup|' . $project_id . '|' . $credits_cents . '|' . $test_run_id);
        if (isset($records[$key]) && is_array($records[$key])) {
            return array('status' => 'reused', 'order' => $records[$key]);
        }
        $wallet = $this->aysa_agent8v3_image_alt_test_wallet_manager();
        if (!$wallet || !method_exists($wallet, 'create_topup_order')) {
            return array('status' => 'unavailable', 'error' => 'wallet_topup_unavailable');
        }
        $order = $wallet->create_topup_order($project_id, $credits_cents, get_current_user_id(), array(
            'require_remote_identity' => true,
            'suppress_admin_notification' => true,
            'source' => $is_agent8_flow ? 'agent8_image_alt_apply' : 'agent8_image_alt_test',
            'order_type' => 'topup',
            'context' => $is_agent8_flow ? 'agent8_image_alt_apply_credit_gate' : 'agent8_image_alt_test_credit_gate',
            'invoice_label' => $is_agent8_flow ? 'Agent8 Image Alt apply top-up' : 'Agent8 Image Alt test top-up',
        ));
        if (is_wp_error($order)) {
            return array('status' => 'unavailable', 'error' => $order->get_error_code());
        }
        if (!is_array($order)) {
            return array('status' => 'unavailable', 'error' => 'invalid_topup_order');
        }
        $public_order = array(
            'id' => intval($order['id'] ?? 0),
            'status' => sanitize_key((string) ($order['status'] ?? 'pending')),
            'credits_cents' => intval($order['credits_cents'] ?? $credits_cents),
            'amount_cents' => intval($order['amount_cents'] ?? $credits_cents),
            'currency' => sanitize_text_field((string) ($order['currency'] ?? 'EUR')),
            'payment_link' => esc_url_raw((string) ($order['payment_link'] ?? '')),
            'created_at' => sanitize_text_field((string) ($order['created_at'] ?? current_time('mysql'))),
        );
        $records[$key] = $public_order;
        update_option('aysa_agent8v3_image_alt_test_topup_orders', array_slice($records, -50, null, true), false);
        $this->aysa_agent8v3_log_image_alt_test_event('topup_order_created', array(
            'test_run_id' => $test_run_id,
            'order_id' => intval($public_order['id']),
            'credits_cents' => intval($public_order['credits_cents']),
            'status' => sanitize_key((string) $public_order['status']),
        ));
        return array('status' => 'created', 'order' => $public_order);
    }

    private function aysa_agent8v3_bill_image_alt_test_result($project_id, $payload, $result, $context = 'test') {
        $project_id = intval($project_id);
        $idempotency_key = sanitize_text_field((string) ($payload['idempotency_key'] ?? ''));
        $context = sanitize_key((string) $context);
        $is_agent8_flow = $context === 'agent8_flow';
        $item_results = is_array($result['item_results'] ?? null) ? $result['item_results'] : array();
        $verified_changed = 0;
        $attachment_ids = array();
        foreach ($item_results as $item) {
            if (!is_array($item)) {
                continue;
            }
            if ((string) ($item['status'] ?? '') === 'verified' && !empty($item['changed'])) {
                $verified_changed++;
                $attachment_ids[] = intval($item['attachment_id'] ?? 0);
            }
        }
        $unit_cost_cents = $this->aysa_agent8v3_image_alt_test_unit_cost_cents();
        $cost_cents = $verified_changed * $unit_cost_cents;
        $base = array(
            'status' => $cost_cents > 0 ? 'pending' : 'not_billable_no_verified_changes',
            'unit_cost_cents' => $unit_cost_cents,
            'verified_changed_count' => $verified_changed,
            'cost_cents' => $cost_cents,
            'idempotency_key' => $idempotency_key,
        );
        if ($project_id <= 0 || $idempotency_key === '' || $cost_cents <= 0) {
            return $base;
        }

        $records = get_option('aysa_agent8v3_image_alt_test_billing_idempotency', array());
        $records = is_array($records) ? $records : array();
        $billing_key = hash('sha256', 'image_alt_' . $context . '_billing|' . $project_id . '|' . $idempotency_key);
        if (isset($records[$billing_key]) && is_array($records[$billing_key])) {
            return array_merge($base, $records[$billing_key], array('status' => 'idempotent_replay'));
        }

        $wallet = $this->aysa_agent8v3_image_alt_test_wallet_manager();
        if (!$wallet || !method_exists($wallet, 'track_action_usage')) {
            return array_merge($base, array('status' => 'billing_failed', 'error' => 'wallet_unavailable'));
        }

        $action_key = $is_agent8_flow ? 'agent8v3_image_alt_apply' : 'agent8v3_image_alt_test_apply';
        $reference = ($is_agent8_flow ? 'agent8v3_image_alt_apply:' : 'agent8v3_image_alt_test:') . substr($idempotency_key, -24);
        $meta = array(
            'billing_policy' => 'verified_changed_images_only_cost_x5',
            'billing_context' => $is_agent8_flow ? 'normal_agent8_flow' : 'controlled_test_mode',
            'billing_idempotency_key' => $billing_key,
            'payload_hash' => sanitize_text_field((string) ($payload['payload_hash'] ?? '')),
            'test_run_id' => sanitize_text_field((string) ($payload['test_run_id'] ?? '')),
            'verified_changed_count' => $verified_changed,
            'unit_cost_cents' => $unit_cost_cents,
            'override_cost_cents' => $cost_cents,
            'attachment_ids' => array_values(array_filter(array_map('intval', $attachment_ids))),
        );
        $ok = $wallet->track_action_usage(
            $project_id,
            $action_key,
            $reference,
            $meta,
            $cost_cents,
            'action:' . $action_key
        );
        if (!$ok) {
            return array_merge($base, array('status' => 'billing_failed', 'error' => 'wallet_debit_failed'));
        }

        $record = array(
            'status' => 'billed',
            'cost_cents' => $cost_cents,
            'unit_cost_cents' => $unit_cost_cents,
            'verified_changed_count' => $verified_changed,
            'reference' => $reference,
            'action_key' => $action_key,
            'billed_at' => current_time('mysql'),
        );
        $records[$billing_key] = $record;
        update_option('aysa_agent8v3_image_alt_test_billing_idempotency', array_slice($records, -100, null, true), false);
        return array_merge($base, $record);
    }

    private function aysa_agent8v3_project_id() {
        $wp_project_id = intval(get_option('aysa_project_id', 0));
        if ($wp_project_id <= 0) {
            $stored_key = trim((string) get_option($this->aysa_test_access_key_option_name(), ''));
            $validation = $stored_key !== '' ? $this->aysa_validate_test_access_key($stored_key, $this->aysa_agent8v3_site_host()) : array();
            $wp_project_id = !empty($validation['valid']) && !empty($validation['project_id'])
                ? intval($validation['project_id'])
                : 0;
        }
        return $wp_project_id > 0 ? 'lab_' . $wp_project_id : 'lab_test';
    }

    private function aysa_agent8v3_site_host() {
        $site_host = (string) wp_parse_url(home_url('/'), PHP_URL_HOST);
        $site_host = strtolower(trim($site_host));
        return preg_replace('/^www\./', '', $site_host);
    }

    private function aysa_agent8v3_technical_redirect_dry_run_bridge($project_id, $payload) {
        $selected_rows = is_array($payload['selected_rows'] ?? null) ? array_values($payload['selected_rows']) : array();
        if ($selected_rows === array() && is_array($payload['row'] ?? null)) {
            $selected_rows = array($payload['row']);
        }
        $idempotency_key = sanitize_text_field((string) ($payload['idempotency_key'] ?? ''));
        $result = array(
            'operation' => 'technical_redirect_dry_run_bridge',
            'status' => 'dry_run_blocked',
            'success' => false,
            'dry_run' => true,
            'writes_enabled' => false,
            'billing_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'active_redirect_created' => false,
            'provider' => 'aysa_managed_redirect_store',
            'blocking_reasons' => array(),
            'checked_at' => current_time('mysql'),
        );

        if (empty($payload['dry_run'])) {
            $result['blocking_reasons'][] = 'dry_run_required';
        }
        if (count($selected_rows) !== 1) {
            $result['blocking_reasons'][] = 'single_rule_only';
        }
        if (!empty($payload['writes_enabled']) || !empty($payload['billing_enabled'])) {
            $result['blocking_reasons'][] = 'writes_and_billing_must_remain_disabled';
        }
        if ($idempotency_key === '') {
            $result['blocking_reasons'][] = 'idempotency_key_required';
        }

        $row = is_array($selected_rows[0] ?? null) ? $selected_rows[0] : array();
        $source_url = esc_url_raw((string) ($row['source_url'] ?? $row['from'] ?? $row['url'] ?? ''));
        $target_url = esc_url_raw((string) ($row['target_url'] ?? $row['destination_url'] ?? $row['to'] ?? $row['recommended_url'] ?? ''));
        if ($source_url === '') {
            $result['blocking_reasons'][] = 'source_missing';
        }
        if ($target_url === '') {
            $result['blocking_reasons'][] = 'target_missing';
        }

        $source_url = $this->aysa_agent8v3_redirect_absolute_url($source_url);
        $target_url = $this->aysa_agent8v3_redirect_absolute_url($target_url);
        $source_path = $this->aysa_agent8v3_redirect_normalized_path($source_url);
        $target_path = $this->aysa_agent8v3_redirect_normalized_path($target_url);
        if ($source_path !== '' && $source_path === $target_path) {
            $result['blocking_reasons'][] = 'loop_risk';
        }
        if (!$this->aysa_agent8v3_redirect_same_site_target($target_url)) {
            $result['blocking_reasons'][] = 'external_target';
        }
        if ($this->aysa_agent8v3_redirect_protected_path($source_path) || $this->aysa_agent8v3_redirect_protected_path($target_path)) {
            $result['blocking_reasons'][] = 'protected_path';
        }
        if (!empty($row['selectable']) || !empty($row['writes_enabled']) || !empty($row['billing_enabled'])) {
            $result['blocking_reasons'][] = 'preview_row_must_remain_non_selectable';
        }

        $source_probe = $source_url !== '' ? $this->aysa_agent8v3_redirect_http_probe($source_url) : array('status_code' => 0, 'error' => 'source_missing');
        $target_probe = $target_url !== '' ? $this->aysa_agent8v3_redirect_http_probe($target_url) : array('status_code' => 0, 'error' => 'target_missing');
        $source_status = intval($source_probe['status_code'] ?? 0);
        $target_status = intval($target_probe['status_code'] ?? 0);
        if ($source_status > 0 && $source_status !== 404) {
            $result['blocking_reasons'][] = 'source_not_404_live';
        }
        if ($target_status !== 200) {
            $result['blocking_reasons'][] = 'target_not_200_live';
        }
        if (intval($target_probe['redirect_chain_length'] ?? 0) > 0) {
            $result['blocking_reasons'][] = 'target_redirect_chain_live';
        }

        $rule_hash = hash('sha256', $source_path . '|301|' . $target_path . '|aysa_managed_redirect_store');
        $rule_id = 'aysa_redirect_' . substr($rule_hash, 0, 20);
        $store = $this->aysa_agent8v3_redirect_draft_store($project_id);
        foreach ($store as $existing) {
            if (!is_array($existing)) {
                continue;
            }
            if ((string) ($existing['normalized_source_path'] ?? '') === $source_path
                && (string) ($existing['rule_id'] ?? '') !== $rule_id
                && (string) ($existing['normalized_target_path'] ?? '') !== $target_path
            ) {
                $result['blocking_reasons'][] = 'draft_conflict_same_source';
                break;
            }
        }

        $blocking = array_values(array_unique(array_filter(array_map('strval', $result['blocking_reasons']))));
        $draft_rule = array(
            'rule_id' => $rule_id,
            'project_id' => intval($project_id),
            'site_host' => $this->aysa_agent8v3_site_host(),
            'provider' => 'aysa_managed_redirect_store',
            'redirect_type' => '301',
            'source_url' => $source_url,
            'normalized_source_path' => $source_path,
            'target_url' => $target_url,
            'normalized_target_path' => $target_path,
            'source_package_id' => sanitize_text_field((string) ($row['package_id'] ?? 'technical_redirects_smart_preview')),
            'source_row_id' => sanitize_text_field((string) ($row['row_id'] ?? '')),
            'rule_hash' => $rule_hash,
            'aysa_rule_id' => $rule_id,
            'ownership_marker' => $this->aysa_agent8v3_redirect_ownership_marker($project_id, $rule_id),
            'status' => $blocking === array() ? 'dry_run_validated' : 'dry_run_blocked',
            'active' => false,
            'runtime_enabled' => false,
            'writes_enabled' => false,
            'billing_enabled' => false,
            'restore_available' => false,
            'verification_status' => $blocking === array() ? 'dry_run_verified' : 'dry_run_blocked',
            'last_verified_at' => current_time('mysql'),
            'idempotency_key' => $idempotency_key,
            'created_at' => current_time('mysql'),
            'updated_at' => current_time('mysql'),
        );
        $history_event = array(
            'history_id' => 'redirect_history_' . substr(hash('sha256', $rule_id . '|' . $idempotency_key . '|' . current_time('mysql')), 0, 20),
            'rule_id' => $rule_id,
            'event' => $draft_rule['status'],
            'before_rule' => null,
            'after_rule' => $draft_rule,
            'actor' => 'agent8v3_redirect_dry_run',
            'idempotency_key' => $idempotency_key,
            'billing_amount' => 0,
            'billing_reference' => '',
            'restore_available' => false,
            'created_at' => current_time('mysql'),
        );

        $store[$rule_id] = $draft_rule;
        $history = $this->aysa_agent8v3_redirect_dry_run_history($project_id);
        $history[] = $history_event;
        $this->aysa_agent8v3_save_redirect_draft_store($project_id, $store);
        $this->aysa_agent8v3_save_redirect_dry_run_history($project_id, $history);

        $result['status'] = $draft_rule['status'];
        $result['success'] = $blocking === array();
        $result['blocking_reasons'] = $blocking;
        $result['draft_rule'] = $draft_rule;
        $result['history_event'] = $history_event;
        $result['persisted'] = true;
        $result['draft_store_key'] = $this->aysa_agent8v3_redirect_draft_store_option_key($project_id);
        $result['history_key'] = $this->aysa_agent8v3_redirect_dry_run_history_option_key($project_id);
        $result['http_verification'] = array(
            'source' => $source_probe,
            'target' => $target_probe,
        );
        return $result;
    }

    private function aysa_agent8v3_redirect_product_gates() {
        $project_gate = get_option('aysa_agent8v3_redirect_project_gate_enabled', '0') === '1';
        $site_gate = get_option('aysa_agent8v3_redirect_apply_enabled', '0') === '1';
        $runtime_gate = get_option('aysa_redirect_runtime_provider_enabled', '0') === '1';
        $kill_switch = $this->aysa_agent8v3_env_flag('AYSA_AGENT8V3_REDIRECT_APPLY_KILL_SWITCH')
            || $this->aysa_agent8v3_env_flag('AYSA_AGENT8V3_TECHNICAL_APPLY_KILL_SWITCH');
        return array(
            'module' => 'redirects',
            'provider' => 'aysa_store_runtime_hook',
            'api_project_gate' => 'agent8_redirect_apply_enabled',
            'plugin_site_gate' => 'aysa_agent8v3_redirect_apply_enabled',
            'runtime_provider_gate' => 'aysa_redirect_runtime_provider_enabled',
            'kill_switch' => 'AYSA_AGENT8V3_REDIRECT_APPLY_KILL_SWITCH',
            'project_gate_enabled' => $project_gate,
            'api_project_gate_enabled' => $project_gate,
            'site_gate_enabled' => $site_gate,
            'plugin_site_gate_enabled' => $site_gate,
            'runtime_provider_enabled' => $runtime_gate,
            'global_kill_switch_active' => $kill_switch,
            'normal_flow_enabled' => $project_gate && $site_gate && $runtime_gate && !$kill_switch,
            'default_state' => 'off',
            'production_default' => 'off',
        );
    }

    public function handle_agent8v3_redirect_product_gates_set() {
        if (!$this->aysa_agent8v3_image_alt_test_authorized()) {
            wp_send_json_error(array('message' => 'Unauthorized'), 403);
        }

        $previous_gates = $this->aysa_agent8v3_redirect_product_gates();
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        if (!empty($_POST['read_only'])) {
            wp_send_json_success(array(
                'product_gates' => $previous_gates,
                'read_only' => true,
                'billing_status' => 'disabled_for_rollout',
                'billing_action_key' => 'agent8v3_redirect_apply',
                'billing_unit_cents' => 0,
                'max_cost_cents' => 0,
            ));
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_gate = !empty($_POST['project_gate_enabled']) && sanitize_text_field((string) wp_unslash($_POST['project_gate_enabled'])) !== '0';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $site_gate = !empty($_POST['site_gate_enabled']) && sanitize_text_field((string) wp_unslash($_POST['site_gate_enabled'])) !== '0';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $runtime_gate = !empty($_POST['runtime_provider_enabled']) && sanitize_text_field((string) wp_unslash($_POST['runtime_provider_enabled'])) !== '0';

        update_option('aysa_agent8v3_redirect_project_gate_enabled', $project_gate ? '1' : '0', false);
        update_option('aysa_agent8v3_redirect_apply_enabled', $site_gate ? '1' : '0', false);
        update_option('aysa_redirect_runtime_provider_enabled', $runtime_gate ? '1' : '0', false);

        wp_send_json_success(array(
            'product_gates' => $this->aysa_agent8v3_redirect_product_gates(),
            'previous_gates' => $previous_gates,
            'billing_status' => 'disabled_for_rollout',
            'billing_action_key' => 'agent8v3_redirect_apply',
            'billing_unit_cents' => 0,
            'max_cost_cents' => 0,
        ));
    }

    public function maybe_serve_agent8v3_redirect_runtime_provider() {
        $project_id = intval(preg_replace('/\D+/', '', $this->aysa_agent8v3_project_id()));
        if ($project_id <= 0) {
            return;
        }
        $gates = $this->aysa_agent8v3_redirect_product_gates();
        if (empty($gates['normal_flow_enabled'])) {
            return;
        }
        $path = $this->aysa_agent8v3_redirect_current_request_path();
        if ($path === '' || $this->aysa_agent8v3_redirect_protected_path($path)) {
            return;
        }
        $store = $this->aysa_agent8v3_redirect_draft_store($project_id);
        foreach ($store as $rule) {
            if (!$this->aysa_agent8v3_redirect_rule_is_runtime_active($rule, $project_id)) {
                continue;
            }
            if ((string) ($rule['normalized_source_path'] ?? '') !== $path) {
                continue;
            }
            $target_url = esc_url_raw((string) ($rule['target_url'] ?? ''));
            if ($target_url === '' || !$this->aysa_agent8v3_redirect_same_site_target($target_url)) {
                continue;
            }
            wp_safe_redirect($target_url, 301, 'AYSA Agent8 Redirect Provider');
            exit;
        }
    }

    private function aysa_agent8v3_redirect_current_request_path() {
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $request_uri = isset($_SERVER['REQUEST_URI']) ? (string) wp_unslash($_SERVER['REQUEST_URI']) : '/';
        $path = (string) wp_parse_url($request_uri, PHP_URL_PATH);
        return $this->aysa_agent8v3_redirect_normalized_path($path !== '' ? $path : '/');
    }

    private function aysa_agent8v3_redirect_rule_is_runtime_active($rule, $project_id) {
        if (!is_array($rule)) {
            return false;
        }
        if ((string) ($rule['provider'] ?? '') !== 'aysa_store_runtime_hook') {
            return false;
        }
        if ((string) ($rule['status'] ?? '') !== 'active') {
            return false;
        }
        if (empty($rule['active']) || empty($rule['runtime_enabled'])) {
            return false;
        }
        if (!empty($rule['restored_at']) || !empty($rule['disabled']) || !empty($rule['conflicted'])) {
            return false;
        }
        $rule_id = sanitize_text_field((string) ($rule['aysa_rule_id'] ?? $rule['rule_id'] ?? ''));
        if ($rule_id === '' || !hash_equals($this->aysa_agent8v3_redirect_ownership_marker($project_id, $rule_id), (string) ($rule['ownership_marker'] ?? ''))) {
            return false;
        }
        return true;
    }

    private function aysa_agent8v3_redirect_ownership_marker($project_id, $rule_id) {
        return hash_hmac('sha256', 'agent8v3_redirect_rule|' . intval($project_id) . '|' . sanitize_key((string) $rule_id), wp_salt('auth'));
    }

    private function aysa_agent8v3_redirect_active_history_option_key($project_id) {
        return 'aysa_agent8v3_redirect_active_history_' . intval($project_id);
    }

    private function aysa_agent8v3_redirect_active_history($project_id) {
        $rows = get_option($this->aysa_agent8v3_redirect_active_history_option_key($project_id), array());
        return is_array($rows) ? array_values($rows) : array();
    }

    private function aysa_agent8v3_save_redirect_active_history($project_id, $rows) {
        $rows = is_array($rows) ? array_values($rows) : array();
        update_option($this->aysa_agent8v3_redirect_active_history_option_key($project_id), array_slice($rows, -300), false);
    }

    private function aysa_agent8v3_redirect_store_snapshot_hash($store) {
        return hash('sha256', wp_json_encode(is_array($store) ? $store : array()));
    }

    private function aysa_agent8v3_redirect_active_history_contract_row($project_id, $rule, $event, $extra = array()) {
        $rule = is_array($rule) ? $rule : array();
        $extra = is_array($extra) ? $extra : array();
        $rule_id = sanitize_text_field((string) ($rule['rule_id'] ?? $rule['aysa_rule_id'] ?? ''));
        return array_merge(array(
            'history_id' => 'redirect_active_history_' . substr(hash('sha256', $rule_id . '|' . $event . '|' . current_time('mysql')), 0, 20),
            'rule_id' => $rule_id,
            'project_id' => intval($project_id),
            'website_id' => intval(get_option('aysa_website_id', 0)),
            'run_id' => sanitize_text_field((string) ($rule['run_id'] ?? '')),
            'task_id' => sanitize_text_field((string) ($rule['task_id'] ?? '')),
            'row_id' => sanitize_text_field((string) ($rule['source_row_id'] ?? '')),
            'source_url' => esc_url_raw((string) ($rule['source_url'] ?? '')),
            'normalized_source_url' => sanitize_text_field((string) ($rule['normalized_source_path'] ?? '')),
            'source_status_before' => sanitize_text_field((string) ($rule['source_status_before'] ?? '404')),
            'source_verified_at_before' => sanitize_text_field((string) ($rule['last_verified_at'] ?? '')),
            'target_url' => esc_url_raw((string) ($rule['target_url'] ?? '')),
            'normalized_target_url' => sanitize_text_field((string) ($rule['normalized_target_path'] ?? '')),
            'target_status_before' => sanitize_text_field((string) ($rule['target_status_before'] ?? '200')),
            'target_final_url_before' => esc_url_raw((string) ($rule['target_final_url_before'] ?? $rule['target_url'] ?? '')),
            'target_indexability' => sanitize_text_field((string) ($rule['target_indexability'] ?? 'unknown')),
            'target_canonical_status' => sanitize_text_field((string) ($rule['target_canonical_status'] ?? 'unknown')),
            'redirect_type' => sanitize_text_field((string) ($rule['redirect_type'] ?? '301')),
            'provider' => 'aysa_store_runtime_hook',
            'provider_rule_id' => $rule_id,
            'aysa_rule_id' => sanitize_text_field((string) ($rule['aysa_rule_id'] ?? $rule_id)),
            'ownership_marker' => sanitize_text_field((string) ($rule['ownership_marker'] ?? '')),
            'old_store_snapshot_hash' => sanitize_text_field((string) ($extra['old_store_snapshot_hash'] ?? '')),
            'new_store_snapshot_hash' => sanitize_text_field((string) ($extra['new_store_snapshot_hash'] ?? '')),
            'backup_reference' => sanitize_text_field((string) ($extra['backup_reference'] ?? '')),
            'created_at' => current_time('mysql'),
            'created_by_agent' => 'agent8v3_redirect_r5_contract',
            'billing_action_key' => 'agent8v3_redirect_apply',
            'billing_reference' => sanitize_text_field((string) ($extra['billing_reference'] ?? '')),
            'billed_amount' => intval($extra['billed_amount'] ?? 0),
            'idempotency_key' => sanitize_text_field((string) ($extra['idempotency_key'] ?? $rule['idempotency_key'] ?? '')),
            'verification_status' => sanitize_text_field((string) ($extra['verification_status'] ?? 'not_verified')),
            'last_verified_at' => sanitize_text_field((string) ($extra['last_verified_at'] ?? '')),
            'restore_available' => !empty($extra['restore_available']),
            'restored_at' => sanitize_text_field((string) ($extra['restored_at'] ?? '')),
            'restore_reference' => sanitize_text_field((string) ($extra['restore_reference'] ?? '')),
            'current_status' => sanitize_text_field((string) ($extra['current_status'] ?? $event)),
        ), $extra);
    }

    private function aysa_agent8v3_technical_redirect_apply_bridge($project_id, $payload) {
        $selected_rows = is_array($payload['selected_rows'] ?? null) ? array_values($payload['selected_rows']) : array();
        if ($selected_rows === array() && is_array($payload['selected_items'] ?? null)) {
            $selected_rows = array_values($payload['selected_items']);
        }
        if ($selected_rows === array() && is_array($payload['row'] ?? null)) {
            $selected_rows = array($payload['row']);
        }
        $gates = $this->aysa_agent8v3_redirect_product_gates();
        $idempotency_key = sanitize_text_field((string) ($payload['idempotency_key'] ?? ''));
        $payload_hash = sanitize_text_field((string) ($payload['payload_hash'] ?? ''));
        $blocking = array();
        $result = array(
            'operation' => 'technical_redirect_apply_bridge',
            'status' => 'blocked',
            'success' => false,
            'module' => 'redirects',
            'provider' => 'aysa_store_runtime_hook',
            'product_gates' => $gates,
            'payload_hash' => $payload_hash,
            'idempotency_key' => $idempotency_key,
            'accepted_count' => count($selected_rows),
            'applied_count' => 0,
            'verified_count' => 0,
            'failed_count' => 0,
            'skipped_count' => 0,
            'item_results' => array(),
            'writes_enabled' => false,
            'active_redirect_created' => false,
            'billing_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'billing_action_key' => 'agent8v3_redirect_apply',
            'billing_policy' => 'existing_redirect_apply_billing_disabled_for_rollout',
            'billing_cost_cents' => 0,
            'actual_credits_cents' => 0,
            'billing_external_recorded' => false,
            'restore_required' => true,
            'readback_required' => true,
            'http_reverify_required' => true,
            'checked_at' => current_time('mysql'),
        );
        if (empty($gates['project_gate_enabled'])) {
            $blocking[] = 'agent8_redirect_apply_enabled_off';
        }
        if (empty($gates['site_gate_enabled'])) {
            $blocking[] = 'aysa_agent8v3_redirect_apply_enabled_off';
        }
        if (empty($gates['runtime_provider_enabled'])) {
            $blocking[] = 'aysa_redirect_runtime_provider_enabled_off';
        }
        if (!empty($gates['global_kill_switch_active'])) {
            $blocking[] = 'redirect_apply_kill_switch_active';
        }
        if (count($selected_rows) !== 1) {
            $blocking[] = 'single_rule_only';
        }
        if ($idempotency_key === '') {
            $blocking[] = 'idempotency_key_required';
        }
        if (!empty($payload['dry_run']) || empty($payload['writes_enabled'])) {
            $blocking[] = 'writes_not_requested';
        }
        if (empty($payload['user_confirmed']) && empty($payload['confirmed_by_user'])) {
            $blocking[] = 'user_confirmation_required';
        }
        if (sanitize_key((string) ($payload['module'] ?? '')) !== 'redirects') {
            $blocking[] = 'module_not_redirects';
        }
        if (sanitize_key((string) ($payload['billing_status'] ?? '')) !== 'disabled_for_rollout') {
            $blocking[] = 'redirect_billing_must_remain_disabled_for_rollout';
        }
        if (sanitize_key((string) ($payload['billing_action_key'] ?? '')) !== 'agent8v3_redirect_apply') {
            $blocking[] = 'redirect_billing_action_required';
        }
        if (intval($payload['billing_unit_cents'] ?? 0) !== 0 || intval($payload['max_cost_cents'] ?? 0) !== 0) {
            $blocking[] = 'redirect_billing_amount_must_be_zero';
        }

        $row = is_array($selected_rows[0] ?? null) ? $selected_rows[0] : array();
        $source_url = $this->aysa_agent8v3_redirect_absolute_url((string) ($row['source_url'] ?? $row['url'] ?? ''));
        $target_url = $this->aysa_agent8v3_redirect_absolute_url((string) ($row['target_url'] ?? $row['proposed_target_url'] ?? ''));
        $source_path = $this->aysa_agent8v3_redirect_normalized_path($source_url);
        $target_path = $this->aysa_agent8v3_redirect_normalized_path($target_url);
        $source_status = intval($row['source_status'] ?? $row['status'] ?? 0);
        $target_status = intval($row['target_status'] ?? 0);
        $risk_flags = is_array($row['risk_flags'] ?? null) ? array_values($row['risk_flags']) : array();
        if ($source_url === '' || $source_path === '') {
            $blocking[] = 'source_missing';
        }
        if ($target_url === '' || $target_path === '') {
            $blocking[] = 'target_missing';
        }
        if ($source_path !== '' && $source_path === $target_path) {
            $blocking[] = 'loop_risk';
        }
        if (!$this->aysa_agent8v3_redirect_same_site_target($target_url)) {
            $blocking[] = 'external_target';
        }
        if ($this->aysa_agent8v3_redirect_protected_path($source_path) || $this->aysa_agent8v3_redirect_protected_path($target_path)) {
            $blocking[] = 'protected_path';
        }
        if (sanitize_text_field((string) ($row['safety_classification'] ?? '')) !== 'safe_301_redirect') {
            $blocking[] = 'safe_301_redirect_required';
        }
        if (sanitize_text_field((string) ($row['redirect_type'] ?? '301')) !== '301') {
            $blocking[] = 'redirect_301_required';
        }
        if ($source_status !== 404 || $target_status !== 200) {
            $blocking[] = 'preview_status_evidence_required';
        }
        if ($risk_flags !== array() || sanitize_text_field((string) ($row['blocked_reason'] ?? '')) !== '') {
            $blocking[] = 'risk_flags_present';
        }
        if (empty($row['accepted_by_user'])) {
            $blocking[] = 'accepted_by_user_required';
        }
        if (empty($row['backup_available']) && empty($row['backup_required'])) {
            $blocking[] = 'backup_required';
        }
        if (empty($row['rollback_available']) && empty($row['rollback_required'])) {
            $blocking[] = 'rollback_required';
        }
        if (empty($row['readback_available']) && empty($row['readback_required']) && empty($row['http_verification_available'])) {
            $blocking[] = 'readback_required';
        }

        $store = $this->aysa_agent8v3_redirect_draft_store($project_id);
        $rule_hash = hash('sha256', $source_path . '|301|' . $target_path . '|aysa_store_runtime_hook');
        $rule_id = 'aysa_redirect_' . substr($rule_hash, 0, 20);
        $items_hash = hash('sha256', wp_json_encode($selected_rows));
        $idempotency_option = 'aysa_agent8v3_redirect_apply_idempotency_' . intval($project_id);
        $records = get_option($idempotency_option, array());
        $records = is_array($records) ? $records : array();
        if ($idempotency_key !== '' && isset($records[$idempotency_key]) && is_array($records[$idempotency_key])) {
            $record = $records[$idempotency_key];
            if (sanitize_text_field((string) ($record['items_hash'] ?? '')) !== $items_hash) {
                $result['status'] = 'idempotency_conflict';
                $result['reason'] = 'idempotency_key_payload_mismatch';
                $result['blocking_reasons'] = array('idempotency_key_payload_mismatch');
                return $result;
            }
            $replay = is_array($record['result'] ?? null) ? $record['result'] : array();
            $replay['idempotent_replay'] = true;
            return array_merge($result, $replay);
        }

        foreach ($store as $existing_id => $existing) {
            if (!is_array($existing) || (string) ($existing['normalized_source_path'] ?? '') !== $source_path) {
                continue;
            }
            if ($this->aysa_agent8v3_redirect_rule_is_runtime_active($existing, $project_id)) {
                if ((string) ($existing['normalized_target_path'] ?? '') === $target_path) {
                    $already = array_merge($result, array(
                        'status' => 'already_redirected',
                        'success' => true,
                        'applied_count' => 1,
                        'verified_count' => 1,
                        'skipped_count' => 0,
                        'writes_enabled' => false,
                        'active_redirect_created' => false,
                        'rule_id' => (string) $existing_id,
                        'rule' => $existing,
                        'blocking_reasons' => array(),
                        'item_results' => array(array(
                            'source_url' => $source_url,
                            'target_url' => $target_url,
                            'redirect_type' => '301',
                            'verification_status' => 'already_redirected',
                            'rule_id' => (string) $existing_id,
                        )),
                    ));
                    if ($idempotency_key !== '') {
                        $records[$idempotency_key] = array(
                            'items_hash' => $items_hash,
                            'result' => $already,
                            'created_at' => current_time('mysql'),
                        );
                        update_option($idempotency_option, array_slice($records, -200, null, true), false);
                    }
                    return $already;
                }
                $blocking[] = 'active_redirect_conflict_same_source';
                break;
            }
        }

        if ($source_url !== '') {
            $source_probe = $this->aysa_agent8v3_redirect_http_probe($source_url);
            $source_probe_location = (string) ($source_probe['location'] ?? '');
            if ($source_probe_location !== '' && !preg_match('~^https?://~i', $source_probe_location)) {
                $source_probe_location = $this->aysa_agent8v3_redirect_absolute_url($source_probe_location);
            }
            $source_probe_location_path = $this->aysa_agent8v3_redirect_normalized_path($source_probe_location);
            if (
                intval($source_probe['status_code'] ?? 0) >= 300
                && intval($source_probe['status_code'] ?? 0) < 400
                && $source_probe_location_path === $target_path
            ) {
                $target_probe = $target_url !== ''
                    ? $this->aysa_agent8v3_redirect_http_probe($target_url)
                    : array('status_code' => 0, 'error' => 'target_missing');
                if (intval($target_probe['status_code'] ?? 0) === 200) {
                    $already = array_merge($result, array(
                        'status' => 'already_redirected',
                        'success' => true,
                        'applied_count' => 1,
                        'verified_count' => 1,
                        'skipped_count' => 0,
                        'writes_enabled' => false,
                        'active_redirect_created' => false,
                        'rule_id' => $rule_id,
                        'rule' => array(),
                        'http_preflight' => array(
                            'source' => $source_probe,
                            'target' => $target_probe,
                        ),
                        'blocking_reasons' => array(),
                        'item_results' => array(array(
                            'source_url' => $source_url,
                            'target_url' => $target_url,
                            'redirect_type' => '301',
                            'verification_status' => 'already_redirected_public_readback',
                            'rule_id' => $rule_id,
                        )),
                    ));
                    if ($idempotency_key !== '') {
                        $records[$idempotency_key] = array(
                            'items_hash' => $items_hash,
                            'result' => $already,
                            'created_at' => current_time('mysql'),
                        );
                        update_option($idempotency_option, array_slice($records, -200, null, true), false);
                    }
                    return $already;
                }
                $blocking[] = 'target_not_200_live';
            } elseif (intval($source_probe['status_code'] ?? 0) !== 404) {
                $blocking[] = 'source_not_404_live';
            }
        } else {
            $source_probe = array('status_code' => 0, 'error' => 'source_missing');
        }
        if ($target_url !== '') {
            $target_probe = $this->aysa_agent8v3_redirect_http_probe($target_url);
            if (intval($target_probe['status_code'] ?? 0) !== 200) {
                $blocking[] = 'target_not_200_live';
            }
            if (intval($target_probe['redirect_chain_length'] ?? 0) > 0) {
                $blocking[] = 'target_redirect_chain_live';
            }
        } else {
            $target_probe = array('status_code' => 0, 'error' => 'target_missing');
        }
        $result['http_preflight'] = array(
            'source' => $source_probe,
            'target' => $target_probe,
        );

        $blocking = array_values(array_unique(array_filter(array_map('strval', $blocking))));
        if ($blocking !== array()) {
            $result['status'] = 'blocked';
            $result['reason'] = $blocking[0];
            $result['blocking_reasons'] = $blocking;
            $result['skipped_count'] = count($selected_rows);
            $result['item_results'][] = array(
                'source_url' => $source_url,
                'target_url' => $target_url,
                'redirect_type' => '301',
                'verification_status' => 'blocked',
                'error' => $blocking[0],
            );
            return $result;
        }

        $before_hash = $this->aysa_agent8v3_redirect_store_snapshot_hash($store);
        $now = current_time('mysql');
        $backup_reference = 'redirect_backup_' . substr(hash('sha256', $rule_id . '|' . $before_hash . '|' . $now), 0, 16);
        $rule = array(
            'rule_id' => $rule_id,
            'project_id' => intval($project_id),
            'website_id' => intval(get_option('aysa_website_id', 0)),
            'site_host' => $this->aysa_agent8v3_site_host(),
            'provider' => 'aysa_store_runtime_hook',
            'redirect_type' => '301',
            'source_url' => $source_url,
            'normalized_source_path' => $source_path,
            'target_url' => $target_url,
            'normalized_target_path' => $target_path,
            'source_package_id' => sanitize_text_field((string) ($row['package_id'] ?? 'technical_redirects_smart_preview')),
            'source_row_id' => sanitize_text_field((string) ($row['row_id'] ?? '')),
            'rule_hash' => $rule_hash,
            'aysa_rule_id' => $rule_id,
            'ownership_marker' => $this->aysa_agent8v3_redirect_ownership_marker($project_id, $rule_id),
            'status' => 'active',
            'active' => true,
            'runtime_enabled' => true,
            'writes_enabled' => true,
            'billing_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'billing_action_key' => 'agent8v3_redirect_apply',
            'billing_reference' => '',
            'restore_available' => true,
            'backup_reference' => $backup_reference,
            'source_status_before' => '404',
            'target_status_before' => '200',
            'target_final_url_before' => $target_url,
            'target_indexability' => sanitize_text_field((string) ($row['target_indexability'] ?? 'indexable')),
            'target_canonical_status' => sanitize_text_field((string) ($row['target_canonical_status'] ?? 'self')),
            'verification_status' => 'pending_public_readback',
            'idempotency_key' => $idempotency_key,
            'created_at' => $now,
            'updated_at' => $now,
        );
        $store[$rule_id] = $rule;
        $this->aysa_agent8v3_save_redirect_draft_store($project_id, $store);

        $source_after = $this->aysa_agent8v3_redirect_http_probe($source_url);
        $target_after = $this->aysa_agent8v3_redirect_http_probe($target_url);
        $location = (string) ($source_after['location'] ?? '');
        if ($location !== '' && !preg_match('~^https?://~i', $location)) {
            $location = $this->aysa_agent8v3_redirect_absolute_url($location);
        }
        $location_path = $this->aysa_agent8v3_redirect_normalized_path($location);
        $readback_ok = intval($source_after['status_code'] ?? 0) >= 300
            && intval($source_after['status_code'] ?? 0) < 400
            && $location_path === $target_path
            && intval($target_after['status_code'] ?? 0) === 200;

        if (!$readback_ok) {
            $store[$rule_id]['active'] = false;
            $store[$rule_id]['runtime_enabled'] = false;
            $store[$rule_id]['status'] = 'verification_failed';
            $store[$rule_id]['verification_status'] = 'verification_failed';
            $store[$rule_id]['updated_at'] = current_time('mysql');
            $after_hash = $this->aysa_agent8v3_redirect_store_snapshot_hash($store);
            $this->aysa_agent8v3_save_redirect_draft_store($project_id, $store);
            $history = $this->aysa_agent8v3_redirect_active_history($project_id);
            $history[] = $this->aysa_agent8v3_redirect_active_history_contract_row($project_id, $store[$rule_id], 'verification_failed', array(
                'old_store_snapshot_hash' => $before_hash,
                'new_store_snapshot_hash' => $after_hash,
                'backup_reference' => $backup_reference,
                'verification_status' => 'verification_failed',
                'restore_available' => false,
                'idempotency_key' => $idempotency_key,
                'current_status' => 'verification_failed',
            ));
            $this->aysa_agent8v3_save_redirect_active_history($project_id, $history);
            $result['status'] = 'failed';
            $result['reason'] = 'public_readback_failed';
            $result['failed_count'] = 1;
            $result['writes_enabled'] = false;
            $result['active_redirect_created'] = false;
            $result['http_verification'] = array('source' => $source_after, 'target' => $target_after, 'location_path' => $location_path);
            $result['rule'] = $store[$rule_id];
            $result['item_results'][] = array(
                'source_url' => $source_url,
                'target_url' => $target_url,
                'redirect_type' => '301',
                'verification_status' => 'failed',
                'error' => 'public_readback_failed',
                'rule_id' => $rule_id,
            );
            return $result;
        }

        $store[$rule_id]['verification_status'] = 'redirect_verified';
        $store[$rule_id]['last_verified_at'] = current_time('mysql');
        $store[$rule_id]['updated_at'] = current_time('mysql');
        $after_hash = $this->aysa_agent8v3_redirect_store_snapshot_hash($store);
        $this->aysa_agent8v3_save_redirect_draft_store($project_id, $store);
        $history = $this->aysa_agent8v3_redirect_active_history($project_id);
        $history[] = $this->aysa_agent8v3_redirect_active_history_contract_row($project_id, $store[$rule_id], 'redirect_verified', array(
            'old_store_snapshot_hash' => $before_hash,
            'new_store_snapshot_hash' => $after_hash,
            'backup_reference' => $backup_reference,
            'verification_status' => 'redirect_verified',
            'last_verified_at' => (string) $store[$rule_id]['last_verified_at'],
            'restore_available' => true,
            'idempotency_key' => $idempotency_key,
            'current_status' => 'active',
        ));
        $this->aysa_agent8v3_save_redirect_active_history($project_id, $history);

        $result['status'] = 'redirect_verified';
        $result['success'] = true;
        $result['blocking_reasons'] = array();
        $result['applied_count'] = 1;
        $result['verified_count'] = 1;
        $result['writes_enabled'] = true;
        $result['active_redirect_created'] = true;
        $result['rule_id'] = $rule_id;
        $result['rule'] = $store[$rule_id];
        $result['history_event'] = end($history);
        $result['http_verification'] = array('source' => $source_after, 'target' => $target_after, 'location_path' => $location_path);
        $result['item_results'][] = array(
            'source_url' => $source_url,
            'target_url' => $target_url,
            'redirect_type' => '301',
            'verification_status' => 'redirect_verified',
            'rule_id' => $rule_id,
            'backup_reference' => $backup_reference,
            'restore_available' => true,
        );
        if ($idempotency_key !== '') {
            $records[$idempotency_key] = array(
                'items_hash' => $items_hash,
                'result' => $result,
                'created_at' => current_time('mysql'),
            );
            update_option($idempotency_option, array_slice($records, -200, null, true), false);
        }
        return $result;
    }

    private function aysa_agent8v3_technical_redirect_restore_bridge($project_id, $payload) {
        $rule_id = sanitize_text_field((string) ($payload['rule_id'] ?? $payload['aysa_rule_id'] ?? ''));
        $store = $this->aysa_agent8v3_redirect_draft_store($project_id);
        $result = array(
            'operation' => 'technical_redirect_restore_bridge',
            'status' => 'rule_not_found',
            'success' => false,
            'module' => 'redirects',
            'rule_id' => $rule_id,
            'writes_enabled' => false,
            'billing_enabled' => false,
            'billing_status' => 'not_billable_restore',
            'billing_action_key' => 'agent8v3_redirect_restore',
            'active_redirect_removed' => false,
            'checked_at' => current_time('mysql'),
        );
        if ($rule_id === '' || !isset($store[$rule_id]) || !is_array($store[$rule_id])) {
            return $result;
        }
        $rule = $store[$rule_id];
        if (!$this->aysa_agent8v3_redirect_rule_is_runtime_active($rule, $project_id)) {
            $result['status'] = !empty($rule['restored_at']) ? 'already_restored' : 'no_active_rule_to_restore';
            $result['success'] = true;
            $result['rule'] = $rule;
            return $result;
        }
        $before_hash = $this->aysa_agent8v3_redirect_store_snapshot_hash($store);
        $rule['active'] = false;
        $rule['runtime_enabled'] = false;
        $rule['status'] = 'restored';
        $rule['restored_at'] = current_time('mysql');
        $rule['restore_reference'] = 'redirect_restore_' . substr(hash('sha256', $rule_id . '|' . current_time('mysql')), 0, 16);
        $rule['updated_at'] = current_time('mysql');
        $store[$rule_id] = $rule;
        $after_hash = $this->aysa_agent8v3_redirect_store_snapshot_hash($store);
        $this->aysa_agent8v3_save_redirect_draft_store($project_id, $store);
        $history = $this->aysa_agent8v3_redirect_active_history($project_id);
        $history[] = $this->aysa_agent8v3_redirect_active_history_contract_row($project_id, $rule, 'restored', array(
            'old_store_snapshot_hash' => $before_hash,
            'new_store_snapshot_hash' => $after_hash,
            'restore_available' => false,
            'restored_at' => (string) $rule['restored_at'],
            'restore_reference' => (string) $rule['restore_reference'],
            'current_status' => 'restored',
            'verification_status' => 'restored',
        ));
        $this->aysa_agent8v3_save_redirect_active_history($project_id, $history);
        $result['status'] = 'restored';
        $result['success'] = true;
        $result['active_redirect_removed'] = true;
        $result['rule'] = $rule;
        return $result;
    }

    private function aysa_agent8v3_technical_redirect_reverify_bridge($project_id, $payload) {
        $rule_id = sanitize_text_field((string) ($payload['rule_id'] ?? $payload['aysa_rule_id'] ?? ''));
        $store = $this->aysa_agent8v3_redirect_draft_store($project_id);
        $result = array(
            'operation' => 'technical_redirect_reverify_bridge',
            'status' => 'no_active_redirects_to_reverify',
            'success' => true,
            'module' => 'redirects',
            'rule_id' => $rule_id,
            'writes_enabled' => false,
            'billing_enabled' => false,
            'billing_status' => 'not_billable_reverify',
            'billing_action_key' => 'agent8v3_redirect_reverify',
            'active_count' => 0,
            'results' => array(),
            'checked_at' => current_time('mysql'),
        );
        foreach ($store as $candidate_id => $rule) {
            if ($rule_id !== '' && (string) $candidate_id !== $rule_id) {
                continue;
            }
            if (!$this->aysa_agent8v3_redirect_rule_is_runtime_active($rule, $project_id)) {
                continue;
            }
            $result['active_count']++;
            $source = $this->aysa_agent8v3_redirect_absolute_url((string) ($rule['source_url'] ?? ''));
            $target = $this->aysa_agent8v3_redirect_absolute_url((string) ($rule['target_url'] ?? ''));
            $source_probe = $source !== '' ? $this->aysa_agent8v3_redirect_http_probe($source) : array('status_code' => 0, 'error' => 'source_missing');
            $target_probe = $target !== '' ? $this->aysa_agent8v3_redirect_http_probe($target) : array('status_code' => 0, 'error' => 'target_missing');
            $status = 'active';
            if (intval($source_probe['status_code'] ?? 0) < 300 || intval($source_probe['status_code'] ?? 0) >= 400) {
                $status = 'source_not_redirecting';
            } elseif (intval($target_probe['status_code'] ?? 0) !== 200) {
                $status = 'target_not_available';
            } elseif (intval($source_probe['redirect_chain_length'] ?? 0) > 1 || intval($target_probe['redirect_chain_length'] ?? 0) > 0) {
                $status = 'chain_detected';
            }
            $result['results'][] = array(
                'rule_id' => (string) $candidate_id,
                'status' => $status,
                'source' => $source_probe,
                'target' => $target_probe,
                'target_url' => $target,
            );
        }
        if ($result['active_count'] > 0) {
            $result['status'] = 'reverified';
            $result['success'] = true;
        }
        return $result;
    }

    private function aysa_agent8v3_redirect_draft_store_option_key($project_id) {
        return 'aysa_agent8v3_redirect_draft_store_' . intval($project_id);
    }

    private function aysa_agent8v3_redirect_dry_run_history_option_key($project_id) {
        return 'aysa_agent8v3_redirect_dry_run_history_' . intval($project_id);
    }

    private function aysa_agent8v3_redirect_draft_store($project_id) {
        $rows = get_option($this->aysa_agent8v3_redirect_draft_store_option_key($project_id), array());
        return is_array($rows) ? $rows : array();
    }

    private function aysa_agent8v3_redirect_dry_run_history($project_id) {
        $rows = get_option($this->aysa_agent8v3_redirect_dry_run_history_option_key($project_id), array());
        return is_array($rows) ? array_values($rows) : array();
    }

    private function aysa_agent8v3_save_redirect_draft_store($project_id, $rows) {
        $rows = is_array($rows) ? $rows : array();
        $rows = array_slice($rows, -200, null, true);
        update_option($this->aysa_agent8v3_redirect_draft_store_option_key($project_id), $rows, false);
    }

    private function aysa_agent8v3_save_redirect_dry_run_history($project_id, $rows) {
        $rows = is_array($rows) ? array_values($rows) : array();
        update_option($this->aysa_agent8v3_redirect_dry_run_history_option_key($project_id), array_slice($rows, -300), false);
    }

    private function aysa_agent8v3_redirect_absolute_url($url) {
        $url = trim((string) $url);
        if ($url === '') {
            return '';
        }
        if (strpos($url, '//') === 0) {
            $url = 'https:' . $url;
        }
        if (!preg_match('~^https?://~i', $url)) {
            $url = home_url('/' . ltrim($url, '/'));
        }
        return esc_url_raw($url);
    }

    private function aysa_agent8v3_redirect_normalized_path($url) {
        $path = (string) wp_parse_url($url, PHP_URL_PATH);
        if ($path === '') {
            $path = '/';
        }
        $path = preg_replace('~/+~', '/', $path);
        $path = '/' . trim((string) $path, '/');
        return $path === '/' ? '/' : rtrim(strtolower($path), '/');
    }

    private function aysa_agent8v3_redirect_same_site_target($target_url) {
        $target_host = strtolower((string) wp_parse_url($target_url, PHP_URL_HOST));
        $site_host = $this->aysa_agent8v3_site_host();
        return $target_host === '' || preg_replace('/^www\./', '', $target_host) === $site_host;
    }

    private function aysa_agent8v3_redirect_protected_path($path) {
        return (bool) preg_match('~/(wp-admin|wp-login\.php|xmlrpc\.php|wp-json|api/|cart|checkout|payment|account|booking|reservation)(/|$)|\.(css|js|png|jpg|jpeg|gif|webp|svg|ico|pdf|zip|php|asp|aspx|jsp|cgi|env|bak|sql)$~i', (string) $path);
    }

    private function aysa_agent8v3_redirect_http_probe($url) {
        $response = wp_remote_request($url, array(
            'method' => 'HEAD',
            'timeout' => 8,
            'redirection' => 0,
            'user-agent' => 'AYSA Agent8 Redirect Dry Run',
        ));
        if (is_wp_error($response) || intval(wp_remote_retrieve_response_code($response)) === 405) {
            $response = wp_remote_request($url, array(
                'method' => 'GET',
                'timeout' => 8,
                'redirection' => 0,
                'user-agent' => 'AYSA Agent8 Redirect Dry Run',
            ));
        }
        if (is_wp_error($response)) {
            return array(
                'url' => esc_url_raw($url),
                'status_code' => 0,
                'redirect_chain_length' => 0,
                'error' => $response->get_error_message(),
            );
        }
        $status = intval(wp_remote_retrieve_response_code($response));
        $location = (string) wp_remote_retrieve_header($response, 'location');
        return array(
            'url' => esc_url_raw($url),
            'status_code' => $status,
            'redirect_chain_length' => ($status >= 300 && $status < 400 && $location !== '') ? 1 : 0,
            'location' => esc_url_raw($location),
            'error' => '',
        );
    }

    private function aysa_agent8v3_technical_image_alt_apply_bridge($project_id, $payload) {
        $selected_items = is_array($payload['selected_items'] ?? null) ? array_values($payload['selected_items']) : array();
        $idempotency_key = sanitize_text_field((string) ($payload['idempotency_key'] ?? ''));
        $payload_hash = sanitize_text_field((string) ($payload['payload_hash'] ?? ''));
        $result = array(
            'operation' => 'technical_image_alt_apply_bridge',
            'module' => 'image_alt',
            'project_id' => intval($project_id),
            'payload_hash' => $payload_hash,
            'idempotency_key' => $idempotency_key,
            'accepted_count' => count($selected_items),
            'applied_count' => 0,
            'verified_count' => 0,
            'applied_pending_verification_count' => 0,
            'failed_count' => 0,
            'skipped_count' => 0,
            'manual_needed_count' => 0,
            'item_results' => array(),
            'dry_run' => !empty($payload['dry_run']),
            'writes_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'billing_cost_cents' => 0,
            'actual_credits_cents' => 0,
        );

        $billing_status = sanitize_key((string) ($payload['billing_status'] ?? ''));
        $test_mode_allowed = $this->aysa_agent8v3_image_alt_test_mode_allows_bridge($payload, count($selected_items));
        $payload_product_gates = isset($payload['product_gates']) && is_array($payload['product_gates']) ? $payload['product_gates'] : array();
        $local_product_gates = $this->aysa_agent8v3_image_alt_product_gates();
        $project_gate_enabled = !empty($payload_product_gates['project_gate_enabled'])
            && !empty($local_product_gates['project_gate_enabled']);
        $site_gate_enabled = !empty($payload_product_gates['site_gate_enabled'])
            && !empty($local_product_gates['site_gate_enabled']);
        $kill_switch_active = !empty($local_product_gates['global_kill_switch_active'])
            || !empty($payload_product_gates['global_kill_switch_active']);
        $normal_agent8_flow_requested = !$test_mode_allowed && $billing_status === 'image_alt_real_billing';
        $normal_agent8_flow_allowed = $normal_agent8_flow_requested
            && $project_gate_enabled
            && $site_gate_enabled
            && !$kill_switch_active;
        $flags = array(
            'global_kill_switch_active' => $kill_switch_active,
            'project_gate_enabled' => $project_gate_enabled || $test_mode_allowed,
            'site_gate_enabled' => $site_gate_enabled || $test_mode_allowed,
            'legacy_env_apply_enabled' => !empty($local_product_gates['legacy_env_apply_enabled']),
            'legacy_env_image_alt_enabled' => !empty($local_product_gates['legacy_env_image_alt_enabled']),
            'legacy_env_image_alt_writes_enabled' => !empty($local_product_gates['legacy_env_image_alt_writes_enabled']),
            'legacy_env_image_alt_verify_enabled' => !empty($local_product_gates['legacy_env_image_alt_verify_enabled']),
            'legacy_env_image_alt_agent8_flow_enabled' => !empty($local_product_gates['legacy_env_image_alt_agent8_flow_enabled']),
        );
        $result['feature_flags'] = $flags;
        $result['product_gates'] = array(
            'payload' => $payload_product_gates,
            'local' => $local_product_gates,
            'project_gate_enabled' => $project_gate_enabled,
            'site_gate_enabled' => $site_gate_enabled,
            'global_kill_switch_active' => $kill_switch_active,
        );
        $result['test_mode_active'] = $test_mode_allowed;
        $result['normal_agent8_flow'] = $normal_agent8_flow_allowed;
        if ($test_mode_allowed) {
            $result['billing_status'] = 'test_mode_real_billing';
        } elseif ($normal_agent8_flow_requested) {
            $result['billing_status'] = 'image_alt_real_billing';
            $result['billing_action_key'] = 'agent8v3_image_alt_apply';
            $result['billing_policy'] = 'verified_changed_images_only_cost_x5';
        }
        $gate_reasons = array();
        if (sanitize_key((string) ($payload['module'] ?? '')) !== 'image_alt') {
            $gate_reasons[] = 'module_not_image_alt';
        }
        if (!empty($payload['test_mode']) && !$test_mode_allowed) {
            $gate_reasons[] = 'image_alt_test_mode_invalid';
        }
        if (empty($payload['user_confirmed'])) {
            $gate_reasons[] = 'user_confirmation_required';
        }
        if ($selected_items === array()) {
            $gate_reasons[] = 'selected_items_required';
        }
        if ($idempotency_key === '') {
            $gate_reasons[] = 'idempotency_key_required';
        }
        if ($test_mode_allowed) {
            if ($billing_status !== 'test_mode_real_billing') {
                $gate_reasons[] = 'billing_must_be_real_for_test_mode';
            }
        } elseif ($normal_agent8_flow_requested) {
            if (!$normal_agent8_flow_allowed) {
                $gate_reasons[] = 'image_alt_agent8_flow_disabled';
            }
            if (!$project_gate_enabled) {
                $gate_reasons[] = 'image_alt_project_gate_disabled';
            }
            if (!$site_gate_enabled) {
                $gate_reasons[] = 'image_alt_site_gate_disabled';
            }
            if ($kill_switch_active) {
                $gate_reasons[] = 'image_alt_global_kill_switch_active';
            }
            if ($billing_status !== 'image_alt_real_billing') {
                $gate_reasons[] = 'image_alt_real_billing_required';
            }
            if (sanitize_key((string) ($payload['billing_action_key'] ?? '')) !== 'agent8v3_image_alt_apply') {
                $gate_reasons[] = 'image_alt_billing_action_required';
            }
        } else {
            $gate_reasons[] = 'image_alt_real_billing_required';
        }
        if (!empty($payload['dry_run']) || empty($payload['writes_enabled'])) {
            $gate_reasons[] = 'writes_not_requested';
        }
        if ($gate_reasons !== array()) {
            $result['status'] = $selected_items === array() ? 'no_executable_fixes' : 'blocked';
            $result['reason'] = $gate_reasons[0];
            $result['gate_reasons'] = $gate_reasons;
            return $result;
        }

        if ($normal_agent8_flow_allowed) {
            $unit_cost_cents = max(1, intval($payload['billing_unit_cents'] ?? $this->aysa_agent8v3_image_alt_test_unit_cost_cents()));
            $max_cost_cents = max(count($selected_items) * $unit_cost_cents, intval($payload['max_cost_cents'] ?? 0));
            $billing_preflight = $this->aysa_agent8v3_image_alt_test_billing_preflight($max_cost_cents, $idempotency_key, 'agent8_flow');
            $result['billing_preflight'] = $billing_preflight;
            if (empty($billing_preflight['ok'])) {
                $result['status'] = 'blocked';
                $result['reason'] = 'image_alt_billing_preflight_failed';
                $result['gate_reasons'] = array('image_alt_billing_preflight_failed');
                $result['billing_status'] = 'billing_preflight_failed';
                $result['writes_enabled'] = false;
                return $result;
            }
        }

        $items_hash = hash('sha256', wp_json_encode($selected_items));
        $records = get_option('aysa_agent8v3_image_alt_apply_idempotency', array());
        $records = is_array($records) ? $records : array();
        if (isset($records[$idempotency_key]) && is_array($records[$idempotency_key])) {
            $record = $records[$idempotency_key];
            if (sanitize_text_field((string) ($record['items_hash'] ?? '')) !== $items_hash) {
                $result['status'] = 'idempotency_conflict';
                $result['reason'] = 'idempotency_key_payload_mismatch';
                return $result;
            }
            $replay = is_array($record['result'] ?? null) ? $record['result'] : array();
            $replay['idempotent_replay'] = true;
            $replay['temporary_product_gates_restored'] = $this->aysa_agent8v3_restore_temporary_image_alt_product_gates($payload);
            return array_merge($result, $replay);
        }

        $apply_context = array(
            'project_id' => intval($project_id),
            'site_host' => $this->aysa_agent8v3_site_host(),
            'test_run_id' => sanitize_text_field((string) ($payload['test_run_id'] ?? '')),
            'idempotency_key' => $idempotency_key,
        );
        foreach ($selected_items as $item) {
            $item_result = $this->aysa_agent8v3_apply_single_image_alt_item($item, $apply_context);
            $result['item_results'][] = $item_result;
            $status = sanitize_key((string) ($item_result['status'] ?? 'failed'));
            if ($status === 'verified') {
                $result['verified_count']++;
                $result['applied_count']++;
            } elseif ($status === 'applied_pending_verification') {
                $result['applied_pending_verification_count']++;
                $result['applied_count']++;
            } elseif (str_starts_with($status, 'skipped')) {
                $result['skipped_count']++;
            } elseif ($status === 'manual_review_needed') {
                $result['manual_needed_count']++;
            } else {
                $result['failed_count']++;
            }
        }

        if ($result['verified_count'] > 0 && $result['failed_count'] === 0 && $result['manual_needed_count'] === 0) {
            $result['status'] = 'verified';
        } elseif ($result['verified_count'] > 0 || $result['applied_pending_verification_count'] > 0) {
            $result['status'] = 'partial_failed';
        } elseif ($result['skipped_count'] > 0 && $result['failed_count'] === 0 && $result['manual_needed_count'] === 0) {
            $result['status'] = 'skipped';
        } elseif ($result['manual_needed_count'] > 0 && $result['failed_count'] === 0) {
            $result['status'] = 'manual_review_needed';
        } else {
            $result['status'] = 'failed';
        }
        $result['writes_enabled'] = true;
        $result['completed_at'] = current_time('mysql');
        if ($normal_agent8_flow_allowed) {
            $billing_result = $this->aysa_agent8v3_bill_image_alt_test_result($project_id, $payload, $result, 'agent8_flow');
            $this->aysa_agent8v3_update_image_alt_history_billing(is_array($result['item_results'] ?? null) ? $result['item_results'] : array(), $billing_result);
            $result['billing'] = $billing_result;
            $result['billing_status'] = sanitize_key((string) ($billing_result['status'] ?? 'billing_not_applicable'));
            $result['billing_action_key'] = 'agent8v3_image_alt_apply';
            $result['billing_cost_cents'] = intval($billing_result['cost_cents'] ?? 0);
            $result['actual_credits_cents'] = intval($billing_result['cost_cents'] ?? 0);
            $result['billing_external_recorded'] = in_array($result['billing_status'], array('billed', 'idempotent_replay', 'not_billable_no_verified_changes'), true);
            $result['billing_reference'] = sanitize_text_field((string) ($billing_result['reference'] ?? ''));
        }
        $records[$idempotency_key] = array(
            'items_hash' => $items_hash,
            'result' => $result,
            'timestamp' => current_time('mysql'),
        );
        update_option('aysa_agent8v3_image_alt_apply_idempotency', array_slice($records, -100, null, true), false);
        $result['temporary_product_gates_restored'] = $this->aysa_agent8v3_restore_temporary_image_alt_product_gates($payload);
        return $result;
    }

    private function aysa_agent8v3_apply_single_image_alt_item($item, $context = array()) {
        $item = is_array($item) ? $item : array();
        $context = is_array($context) ? $context : array();
        $attachment_id = intval($item['attachment_id'] ?? $item['media_id'] ?? 0);
        $proposed_alt = sanitize_text_field((string) ($item['proposed_alt'] ?? ''));
        $old_alt = sanitize_text_field((string) ($item['old_alt'] ?? ''));
        $item_result = array(
            'item_id' => sanitize_text_field((string) ($item['item_id'] ?? $attachment_id)),
            'attachment_id' => $attachment_id,
            'old_alt' => $old_alt,
            'new_alt' => $proposed_alt,
            'changed' => false,
            'verification_status' => 'not_verified',
        );
        if (empty($item['accepted_by_user'])) {
            return array_merge($item_result, array('status' => 'skipped_not_accepted', 'skipped_reason' => 'item_not_accepted'));
        }
        if ($attachment_id <= 0 || get_post_type($attachment_id) !== 'attachment') {
            return array_merge($item_result, array('status' => 'skipped_missing_item', 'skipped_reason' => 'attachment_missing'));
        }
        $current_alt = sanitize_text_field((string) get_post_meta($attachment_id, '_wp_attachment_image_alt', true));
        $item_result['old_alt'] = $current_alt;
        $history_id = $this->aysa_agent8v3_create_image_alt_history_row($item, $context, $current_alt);
        $item_result['history_id'] = $history_id;
        if ($proposed_alt === '') {
            $final = array_merge($item_result, array('status' => 'failed', 'error_code' => 'proposed_alt_empty'));
            $this->aysa_agent8v3_update_image_alt_history_row($history_id, array(
                'status' => $this->aysa_agent8v3_image_alt_history_status_from_item_result($final),
                'final_alt' => $current_alt,
                'verification_status' => 'failed',
                'error_code' => 'proposed_alt_empty',
            ));
            return $final;
        }
        if (array_key_exists('old_alt', $item) && $current_alt !== $old_alt) {
            $final = array_merge($item_result, array('status' => 'manual_review_needed', 'skipped_reason' => 'live_old_alt_differs'));
            $this->aysa_agent8v3_update_image_alt_history_row($history_id, array(
                'status' => $this->aysa_agent8v3_image_alt_history_status_from_item_result($final),
                'final_alt' => $current_alt,
                'verification_status' => 'manual_review_needed',
                'error_code' => 'live_old_alt_differs',
            ));
            return $final;
        }
        if ($current_alt === $proposed_alt) {
            $final = array_merge($item_result, array('status' => 'skipped_no_change', 'verification_status' => 'verified_no_change'));
            $this->aysa_agent8v3_update_image_alt_history_row($history_id, array(
                'status' => $this->aysa_agent8v3_image_alt_history_status_from_item_result($final),
                'final_alt' => $current_alt,
                'verification_status' => 'verified_no_change',
                'billing_status' => 'not_billable_no_change',
                'billing_cost_cents' => 0,
            ));
            return $final;
        }

        update_post_meta($attachment_id, '_wp_attachment_image_alt', $proposed_alt);
        update_post_meta($attachment_id, '_aysa_image_alt_generated_at', current_time('mysql'));
        update_post_meta($attachment_id, '_aysa_image_alt_source', 'agent8v3_technical_apply');
        update_post_meta($attachment_id, '_aysa_image_alt_context_url', esc_url_raw((string) ($item['page_url'] ?? '')));
        update_post_meta($attachment_id, '_aysa_image_alt_hash', hash('sha256', $attachment_id . '|' . $proposed_alt));

        $verified_alt = sanitize_text_field((string) get_post_meta($attachment_id, '_wp_attachment_image_alt', true));
        if ($verified_alt === $proposed_alt) {
            $final = array_merge($item_result, array('status' => 'verified', 'changed' => true, 'verification_status' => 'verified'));
            $this->aysa_agent8v3_update_image_alt_history_row($history_id, array(
                'status' => $this->aysa_agent8v3_image_alt_history_status_from_item_result($final),
                'final_alt' => $verified_alt,
                'verification_status' => 'verified',
            ));
            $this->aysa_agent8v3_log_image_alt_test_event('image_alt_changed', array(
                'history_id' => $history_id,
                'attachment_id' => $attachment_id,
            ));
            return $final;
        }
        $final = array_merge($item_result, array('status' => 'failed', 'changed' => true, 'error_code' => 'verification_mismatch', 'verification_status' => 'failed'));
        $this->aysa_agent8v3_update_image_alt_history_row($history_id, array(
            'status' => $this->aysa_agent8v3_image_alt_history_status_from_item_result($final),
            'final_alt' => $verified_alt,
            'verification_status' => 'failed',
            'error_code' => 'verification_mismatch',
        ));
        return $final;
    }

    private function aysa_agent8v3_canonical_product_gates() {
        $project_gate = get_option('aysa_agent8v3_canonical_project_gate_enabled', '0') === '1';
        $site_gate = get_option('aysa_agent8v3_canonical_apply_enabled', '0') === '1'
            || get_option('aysa_agent8v3_canonical_site_gate_enabled', '0') === '1';
        $kill_switch = $this->aysa_agent8v3_env_flag('AYSA_AGENT8V3_TECHNICAL_APPLY_KILL_SWITCH')
            || $this->aysa_agent8v3_env_flag('AYSA_AGENT8V3_CANONICAL_APPLY_KILL_SWITCH');
        return array(
            'module' => 'canonical',
            'project_gate_enabled' => $project_gate,
            'api_project_gate_enabled' => $project_gate,
            'site_gate_enabled' => $site_gate,
            'plugin_site_gate_enabled' => $site_gate,
            'global_kill_switch_active' => $kill_switch,
            'normal_flow_enabled' => $project_gate && $site_gate && !$kill_switch,
            'default_state' => 'off',
            'source' => 'plugin_admin_options',
        );
    }

    public function handle_agent8v3_canonical_product_gates_set() {
        if (!$this->aysa_agent8v3_image_alt_test_authorized()) {
            wp_send_json_error(array('message' => 'Unauthorized'), 403);
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_gate = !empty($_POST['project_gate_enabled']) && sanitize_text_field((string) wp_unslash($_POST['project_gate_enabled'])) !== '0';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $site_gate = !empty($_POST['site_gate_enabled']) && sanitize_text_field((string) wp_unslash($_POST['site_gate_enabled'])) !== '0';

        update_option('aysa_agent8v3_canonical_project_gate_enabled', $project_gate ? '1' : '0', false);
        update_option('aysa_agent8v3_canonical_site_gate_enabled', $site_gate ? '1' : '0', false);
        update_option('aysa_agent8v3_canonical_apply_enabled', $site_gate ? '1' : '0', false);

        wp_send_json_success(array(
            'product_gates' => $this->aysa_agent8v3_canonical_product_gates(),
            'billing_status' => 'canonical_apply_real_billing',
            'billing_unit_cents' => 5,
        ));
    }

    private function aysa_agent8v3_technical_canonical_apply_bridge($project_id, $payload) {
        $selected_items = is_array($payload['selected_items'] ?? null) ? array_values($payload['selected_items']) : array();
        $idempotency_key = sanitize_text_field((string) ($payload['idempotency_key'] ?? ''));
        $payload_hash = sanitize_text_field((string) ($payload['payload_hash'] ?? ''));
        $result = array(
            'operation' => 'technical_canonical_apply_bridge',
            'module' => 'canonical',
            'project_id' => intval($project_id),
            'payload_hash' => $payload_hash,
            'idempotency_key' => $idempotency_key,
            'accepted_count' => count($selected_items),
            'applied_count' => 0,
            'verified_count' => 0,
            'applied_pending_verification_count' => 0,
            'failed_count' => 0,
            'skipped_count' => 0,
            'manual_needed_count' => 0,
            'item_results' => array(),
            'dry_run' => !empty($payload['dry_run']),
            'writes_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'billing_action_key' => 'agent8v3_canonical_apply',
            'billing_cost_cents' => 0,
            'actual_credits_cents' => 0,
        );

        $billing_status = sanitize_key((string) ($payload['billing_status'] ?? ''));
        $payload_product_gates = isset($payload['product_gates']) && is_array($payload['product_gates']) ? $payload['product_gates'] : array();
        $local_product_gates = $this->aysa_agent8v3_canonical_product_gates();
        $project_gate_enabled = !empty($payload_product_gates['project_gate_enabled']) && !empty($local_product_gates['project_gate_enabled']);
        $site_gate_enabled = !empty($payload_product_gates['site_gate_enabled']) && !empty($local_product_gates['site_gate_enabled']);
        $kill_switch_active = !empty($local_product_gates['global_kill_switch_active']) || !empty($payload_product_gates['global_kill_switch_active']);
        $normal_agent8_flow_allowed = $billing_status === 'canonical_apply_real_billing'
            && $project_gate_enabled
            && $site_gate_enabled
            && !$kill_switch_active;
        $result['billing_status'] = $billing_status === 'canonical_apply_real_billing' ? 'canonical_apply_real_billing' : 'canonical_apply_real_billing_blocked';
        $result['billing_policy'] = 'verified_changed_self_canonical_only_cost_x5';
        $result['product_gates'] = array(
            'payload' => $payload_product_gates,
            'local' => $local_product_gates,
            'project_gate_enabled' => $project_gate_enabled,
            'site_gate_enabled' => $site_gate_enabled,
            'global_kill_switch_active' => $kill_switch_active,
        );
        $gate_reasons = array();
        if (sanitize_key((string) ($payload['module'] ?? '')) !== 'canonical') {
            $gate_reasons[] = 'module_not_canonical';
        }
        if (empty($payload['user_confirmed'])) {
            $gate_reasons[] = 'user_confirmation_required';
        }
        if ($selected_items === array()) {
            $gate_reasons[] = 'selected_canonical_rows_required';
        }
        if ($idempotency_key === '') {
            $gate_reasons[] = 'idempotency_key_required';
        }
        if (!$project_gate_enabled) {
            $gate_reasons[] = 'canonical_project_gate_disabled';
        }
        if (!$site_gate_enabled) {
            $gate_reasons[] = 'canonical_site_gate_disabled';
        }
        if ($kill_switch_active) {
            $gate_reasons[] = 'canonical_global_kill_switch_active';
        }
        if ($billing_status !== 'canonical_apply_real_billing') {
            $gate_reasons[] = 'canonical_real_billing_required';
        }
        if (sanitize_key((string) ($payload['billing_action_key'] ?? '')) !== 'agent8v3_canonical_apply') {
            $gate_reasons[] = 'canonical_billing_action_required';
        }
        if (!empty($payload['dry_run']) || empty($payload['writes_enabled'])) {
            $gate_reasons[] = 'writes_not_requested';
        }
        if (!$normal_agent8_flow_allowed && $gate_reasons === array()) {
            $gate_reasons[] = 'canonical_agent8_flow_disabled';
        }
        if ($gate_reasons !== array()) {
            $result['status'] = $selected_items === array() ? 'no_executable_fixes' : 'blocked';
            $result['reason'] = $gate_reasons[0];
            $result['gate_reasons'] = array_values(array_unique($gate_reasons));
            return $result;
        }

        $unit_cost_cents = max(1, intval($payload['billing_unit_cents'] ?? 5));
        $max_cost_cents = max(count($selected_items) * $unit_cost_cents, intval($payload['max_cost_cents'] ?? 0));
        $billing_preflight = $this->aysa_agent8v3_image_alt_test_billing_preflight($max_cost_cents, $idempotency_key, 'canonical_agent8_flow');
        $result['billing_preflight'] = $billing_preflight;
        if (empty($billing_preflight['ok'])) {
            $result['status'] = 'blocked';
            $result['reason'] = 'canonical_billing_preflight_failed';
            $result['gate_reasons'] = array('canonical_billing_preflight_failed');
            $result['billing_status'] = 'billing_preflight_failed';
            return $result;
        }

        $items_hash = hash('sha256', wp_json_encode($selected_items));
        $records = get_option('aysa_agent8v3_canonical_apply_idempotency', array());
        $records = is_array($records) ? $records : array();
        if (isset($records[$idempotency_key]) && is_array($records[$idempotency_key])) {
            $record = $records[$idempotency_key];
            if (sanitize_text_field((string) ($record['items_hash'] ?? '')) !== $items_hash) {
                $result['status'] = 'idempotency_conflict';
                $result['reason'] = 'idempotency_key_payload_mismatch';
                return $result;
            }
            $replay = is_array($record['result'] ?? null) ? $record['result'] : array();
            $replay['idempotent_replay'] = true;
            return array_merge($result, $replay);
        }

        $apply_context = array(
            'project_id' => intval($project_id),
            'site_host' => $this->aysa_agent8v3_site_host(),
            'idempotency_key' => $idempotency_key,
            'payload_hash' => $payload_hash,
            'source_run_id' => intval($payload['parent_technical_run_id'] ?? $payload['run_id'] ?? 0),
        );
        foreach ($selected_items as $item) {
            $item_result = $this->aysa_agent8v3_apply_single_canonical_item($item, $apply_context);
            $result['item_results'][] = $item_result;
            $status = sanitize_key((string) ($item_result['status'] ?? 'failed'));
            if ($status === 'verified') {
                $result['verified_count']++;
                $result['applied_count']++;
            } elseif (str_starts_with($status, 'skipped')) {
                $result['skipped_count']++;
            } elseif ($status === 'manual_review_needed') {
                $result['manual_needed_count']++;
            } else {
                $result['failed_count']++;
            }
        }

        if ($result['verified_count'] > 0 && $result['failed_count'] === 0 && $result['manual_needed_count'] === 0) {
            $result['status'] = 'verified';
        } elseif ($result['verified_count'] > 0) {
            $result['status'] = 'partial_failed';
        } elseif ($result['skipped_count'] > 0 && $result['failed_count'] === 0 && $result['manual_needed_count'] === 0) {
            $result['status'] = 'skipped';
        } elseif ($result['manual_needed_count'] > 0 && $result['failed_count'] === 0) {
            $result['status'] = 'manual_review_needed';
        } else {
            $result['status'] = 'failed';
        }
        $result['writes_enabled'] = true;
        $result['completed_at'] = current_time('mysql');
        $billing_result = $this->aysa_agent8v3_bill_canonical_apply_result($project_id, $payload, $result);
        $this->aysa_agent8v3_update_canonical_history_billing(is_array($result['item_results'] ?? null) ? $result['item_results'] : array(), $billing_result);
        $result['billing'] = $billing_result;
        $result['billing_status'] = sanitize_key((string) ($billing_result['status'] ?? 'billing_not_applicable'));
        $result['billing_cost_cents'] = intval($billing_result['cost_cents'] ?? 0);
        $result['actual_credits_cents'] = intval($billing_result['cost_cents'] ?? 0);
        $result['billing_external_recorded'] = in_array($result['billing_status'], array('billed', 'idempotent_replay', 'not_billable_no_verified_changes'), true);
        $result['billing_reference'] = sanitize_text_field((string) ($billing_result['reference'] ?? ''));

        $records[$idempotency_key] = array(
            'items_hash' => $items_hash,
            'result' => $result,
            'timestamp' => current_time('mysql'),
        );
        update_option('aysa_agent8v3_canonical_apply_idempotency', array_slice($records, -100, null, true), false);
        return $result;
    }

    private function aysa_agent8v3_apply_single_canonical_item($item, $context = array()) {
        $item = is_array($item) ? $item : array();
        $context = is_array($context) ? $context : array();
        $post_id = intval($item['post_id'] ?? 0);
        $url = esc_url_raw((string) ($item['url'] ?? ''));
        $provider = sanitize_key((string) ($item['provider'] ?? 'unknown'));
        $old_value = esc_url_raw((string) ($item['old_value'] ?? $item['current_canonical'] ?? ''));
        $proposed_value = esc_url_raw((string) ($item['proposed_value'] ?? $item['proposed_canonical'] ?? ''));
        $item_result = array(
            'item_id' => sanitize_text_field((string) ($item['item_id'] ?? $item['row_id'] ?? $post_id . '_canonical')),
            'post_id' => $post_id,
            'url' => $url,
            'provider' => $provider,
            'old_value' => $old_value,
            'proposed_value' => $proposed_value,
            'actual_value' => '',
            'changed' => false,
            'verification_status' => 'not_verified',
        );
        if (empty($item['accepted_by_user'])) {
            return array_merge($item_result, array('status' => 'skipped_not_accepted', 'skipped_reason' => 'item_not_accepted'));
        }
        if ($post_id <= 0 || !get_post($post_id)) {
            return array_merge($item_result, array('status' => 'skipped_missing_item', 'skipped_reason' => 'post_missing'));
        }
        if (sanitize_key((string) ($item['reason'] ?? '')) !== 'missing_self_canonical') {
            return array_merge($item_result, array('status' => 'manual_review_needed', 'skipped_reason' => 'canonical_reason_not_allowed'));
        }
        if (!$this->aysa_agent8v3_canonical_url_is_safe_self_target($url, $proposed_value, $post_id)) {
            return array_merge($item_result, array('status' => 'manual_review_needed', 'skipped_reason' => 'canonical_target_not_safe_self_url'));
        }
        if ($this->aysa_agent8v3_post_has_noindex_signal($post_id)) {
            return array_merge($item_result, array('status' => 'manual_review_needed', 'skipped_reason' => 'noindex_conflict_detected'));
        }
        $key_info = $this->aysa_agent8v3_canonical_write_key($provider);
        if (empty($key_info['ok'])) {
            return array_merge($item_result, array('status' => 'manual_review_needed', 'skipped_reason' => sanitize_key((string) ($key_info['reason'] ?? 'provider_conflict'))));
        }
        $meta_key = sanitize_key((string) ($key_info['primary_key'] ?? ''));
        if ($meta_key === '') {
            return array_merge($item_result, array('status' => 'manual_review_needed', 'skipped_reason' => 'canonical_key_missing'));
        }
        $current_value = esc_url_raw((string) get_post_meta($post_id, $meta_key, true));
        $item_result['old_value'] = $current_value;
        $history_id = $this->aysa_agent8v3_create_canonical_history_row($item, $context, $meta_key, (string) ($key_info['provider'] ?? $provider), $current_value);
        $item_result['history_id'] = $history_id;
        if (array_key_exists('old_value', $item) && !empty($item['old_value_captured']) && $current_value !== $old_value) {
            $final = array_merge($item_result, array('status' => 'manual_review_needed', 'skipped_reason' => 'live_old_value_differs', 'actual_value' => $current_value));
            $this->aysa_agent8v3_update_canonical_history_row($history_id, array(
                'status' => 'skipped',
                'actual_value' => $current_value,
                'verification_status' => 'manual_review_needed',
                'error_code' => 'live_old_value_differs',
            ));
            return $final;
        }
        if ($current_value !== '') {
            $final = array_merge($item_result, array('status' => 'manual_review_needed', 'skipped_reason' => 'canonical_already_exists', 'actual_value' => $current_value));
            $this->aysa_agent8v3_update_canonical_history_row($history_id, array(
                'status' => 'skipped',
                'actual_value' => $current_value,
                'verification_status' => 'manual_review_needed',
                'error_code' => 'canonical_already_exists',
            ));
            return $final;
        }
        if ($current_value === $proposed_value) {
            $final = array_merge($item_result, array('status' => 'skipped_no_change', 'verification_status' => 'verified_no_change', 'actual_value' => $current_value));
            $this->aysa_agent8v3_update_canonical_history_row($history_id, array(
                'status' => 'no_change',
                'actual_value' => $current_value,
                'verification_status' => 'verified_no_change',
                'billing_status' => 'not_billable_no_change',
                'billing_cost_cents' => 0,
            ));
            return $final;
        }

        update_post_meta($post_id, $meta_key, $proposed_value);
        update_post_meta($post_id, '_aysa_agent8v3_canonical_apply_source', 'agent8v3_technical_apply');
        update_post_meta($post_id, '_aysa_agent8v3_canonical_apply_hash', hash('sha256', $post_id . '|canonical|' . $proposed_value));
        update_post_meta($post_id, '_aysa_agent8v3_canonical_apply_updated_at', current_time('mysql'));

        $verified_value = esc_url_raw((string) get_post_meta($post_id, $meta_key, true));
        if ($this->aysa_agent8v3_normalize_url_for_canonical($verified_value) === $this->aysa_agent8v3_normalize_url_for_canonical($proposed_value)) {
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reviewed WordPress query: the metadata selector is required by the feature and is constrained by the surrounding post type, status, limit, or administrative scope.
            $final = array_merge($item_result, array('status' => 'verified', 'changed' => true, 'verification_status' => 'verified', 'actual_value' => $verified_value, 'meta_key' => $meta_key, 'provider' => (string) ($key_info['provider'] ?? $provider)));
            $this->aysa_agent8v3_update_canonical_history_row($history_id, array(
                'status' => 'changed',
                'actual_value' => $verified_value,
                'verification_status' => 'verified',
                // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reviewed WordPress query: the metadata selector is required by the feature and is constrained by the surrounding post type, status, limit, or administrative scope.
                'meta_key' => $meta_key,
                'provider' => (string) ($key_info['provider'] ?? $provider),
            ));
            return $final;
        }
        $final = array_merge($item_result, array('status' => 'failed', 'changed' => true, 'error_code' => 'verification_mismatch', 'verification_status' => 'failed', 'actual_value' => $verified_value));
        $this->aysa_agent8v3_update_canonical_history_row($history_id, array(
            'status' => 'failed',
            'actual_value' => $verified_value,
            'verification_status' => 'failed',
            'error_code' => 'verification_mismatch',
        ));
        return $final;
    }

    private function aysa_agent8v3_canonical_write_key($provider) {
        $provider = sanitize_key((string) $provider);
        $active = $this->aysa_agent8v3_active_canonical_provider();
        if ($provider === '' || $provider === 'unknown') {
            $provider = $active !== '' ? $active : 'aysa';
        }
        if (in_array($provider, array('yoast', 'rank_math'), true) && $active !== $provider) {
            return array('ok' => false, 'reason' => 'provider_conflict');
        }
        if ($provider === 'yoast') {
            return array('ok' => true, 'provider' => 'yoast', 'primary_key' => '_yoast_wpseo_canonical');
        }
        if ($provider === 'rank_math') {
            return array('ok' => true, 'provider' => 'rank_math', 'primary_key' => 'rank_math_canonical_url');
        }
        if ($active !== '' && $provider === 'aysa') {
            return array('ok' => false, 'reason' => 'provider_conflict');
        }
        return array('ok' => true, 'provider' => 'aysa', 'primary_key' => 'aysa_canonical_url');
    }

    private function aysa_agent8v3_active_canonical_provider() {
        if (defined('WPSEO_VERSION') || class_exists('WPSEO_Options') || defined('WPSEO_FILE')) {
            return 'yoast';
        }
        if (defined('RANK_MATH_VERSION') || class_exists('RankMath')) {
            return 'rank_math';
        }
        if (function_exists('is_plugin_active')) {
            if (is_plugin_active('wordpress-seo/wp-seo.php') || is_plugin_active('wordpress-seo-premium/wp-seo-premium.php')) {
                return 'yoast';
            }
            if (is_plugin_active('seo-by-rank-math/rank-math.php')) {
                return 'rank_math';
            }
        }
        return '';
    }

    private function aysa_agent8v3_canonical_url_is_safe_self_target($url, $proposed_value, $post_id) {
        $url = esc_url_raw((string) $url);
        $proposed_value = esc_url_raw((string) $proposed_value);
        if ($url === '' || $proposed_value === '') {
            return false;
        }
        if ($this->aysa_agent8v3_normalize_url_for_canonical($url) !== $this->aysa_agent8v3_normalize_url_for_canonical($proposed_value)) {
            return false;
        }
        $path = trim((string) wp_parse_url($url, PHP_URL_PATH), '/');
        if ($path === '') {
            return false;
        }
        if ((int) get_option('page_on_front') === intval($post_id)) {
            return false;
        }
        if (wp_parse_url($url, PHP_URL_QUERY)) {
            return false;
        }
        return preg_match('#(^|/)page/[0-9]+/?$#i', $path) !== 1
            && strpos(strtolower($path), 'filter') === false
            && strpos(strtolower($path), 'sort') === false;
    }

    private function aysa_agent8v3_normalize_url_for_canonical($url) {
        $url = trim((string) $url);
        $parts = wp_parse_url($url);
        if (!is_array($parts) || empty($parts['host'])) {
            return '';
        }
        $scheme = strtolower((string) ($parts['scheme'] ?? 'https'));
        $host = strtolower((string) $parts['host']);
        $host = preg_replace('/^www\./', '', $host);
        $path = '/' . ltrim((string) ($parts['path'] ?? '/'), '/');
        $path = rtrim($path, '/');
        if ($path === '') {
            $path = '/';
        }
        return $scheme . '://' . $host . $path;
    }

    private function aysa_agent8v3_post_has_noindex_signal($post_id) {
        $post_id = intval($post_id);
        if ($post_id <= 0) {
            return true;
        }
        $yoast_noindex = get_post_meta($post_id, '_yoast_wpseo_meta-robots-noindex', true);
        if ((string) $yoast_noindex === '1') {
            return true;
        }
        $rank_math_robots = get_post_meta($post_id, 'rank_math_robots', true);
        if (is_array($rank_math_robots) && in_array('noindex', array_map('strval', $rank_math_robots), true)) {
            return true;
        }
        if (is_string($rank_math_robots) && stripos($rank_math_robots, 'noindex') !== false) {
            return true;
        }
        return get_post_meta($post_id, '_aysa_robots_noindex', true) === '1';
    }

    private function aysa_agent8v3_canonical_history_rows() {
        $rows = get_option('aysa_agent8v3_canonical_apply_history', array());
        return is_array($rows) ? array_values(array_filter($rows, 'is_array')) : array();
    }

    private function aysa_agent8v3_save_canonical_history_rows($rows) {
        $rows = is_array($rows) ? array_values(array_filter($rows, 'is_array')) : array();
        update_option('aysa_agent8v3_canonical_apply_history', array_slice($rows, -200), false);
    }

    private function aysa_agent8v3_create_canonical_history_row($item, $context, $meta_key, $provider, $current_value) {
        $item = is_array($item) ? $item : array();
        $context = is_array($context) ? $context : array();
        $history_id = 'cah_' . time() . '_' . wp_generate_password(8, false, false);
        $row = array(
            'source' => 'agent8v3_canonical_apply',
            'history_id' => $history_id,
            'project_id' => intval($context['project_id'] ?? 0),
            'site_host' => sanitize_text_field((string) ($context['site_host'] ?? $this->aysa_agent8v3_site_host())),
            'post_id' => intval($item['post_id'] ?? 0),
            'url' => esc_url_raw((string) ($item['url'] ?? '')),
            'provider' => sanitize_key((string) $provider),
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reviewed WordPress query: the metadata selector is required by the feature and is constrained by the surrounding post type, status, limit, or administrative scope.
            'meta_key' => sanitize_key((string) $meta_key),
            'old_value' => esc_url_raw((string) $current_value),
            'new_value' => esc_url_raw((string) ($item['proposed_value'] ?? $item['proposed_canonical'] ?? '')),
            'actual_value' => '',
            'source_run_id' => intval($context['source_run_id'] ?? 0),
            'payload_hash' => sanitize_text_field((string) ($context['payload_hash'] ?? '')),
            'idempotency_key' => sanitize_text_field((string) ($context['idempotency_key'] ?? '')),
            'timestamp' => current_time('mysql'),
            'updated_at' => current_time('mysql'),
            'billing_action_key' => 'agent8v3_canonical_apply',
            'billing_status' => 'pending_result',
            'billing_cost_cents' => 0,
            'verification_status' => 'pending',
            'status' => 'submitted',
            'restore_available' => true,
            'restored_at' => '',
            'error_code' => '',
            'backup_required' => !empty($item['backup_required']) ? '1' : '0',
            'backup_id' => sanitize_text_field((string) ($item['backup_id'] ?? '')),
            'backup_kind' => $this->aysa_agent8v3_schema_item_is_explicit_consolidation($item) ? 'schema_consolidation_apply' : 'schema_apply',
            'rollback_required' => !empty($item['rollback_required']) ? '1' : '0',
            'rollback_scope' => 'aysa_schema_table_only',
            'external_schema_removal' => 'not_attempted',
            'approval_token' => sanitize_text_field((string) ($item['approval_token'] ?? '')),
            'public_readback_hash' => sanitize_text_field((string) ($item['public_readback_hash'] ?? '')),
            'proposed_jsonld_hash' => sanitize_text_field((string) ($item['proposed_jsonld_hash'] ?? '')),
            'public_readback_backup_json' => isset($item['public_readback_backup']) && is_array($item['public_readback_backup'])
                ? (string) wp_json_encode($item['public_readback_backup'], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
                : (isset($item['public_readback']) && is_array($item['public_readback']) ? (string) wp_json_encode($item['public_readback'], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : ''),
            'public_readback_after_json' => '',
            'schema_result_status' => '',
        );
        $rows = $this->aysa_agent8v3_canonical_history_rows();
        $rows[] = $row;
        $this->aysa_agent8v3_save_canonical_history_rows($rows);
        return $history_id;
    }

    private function aysa_agent8v3_update_canonical_history_row($history_id, $updates) {
        $history_id = sanitize_text_field((string) $history_id);
        if ($history_id === '') {
            return;
        }
        $rows = $this->aysa_agent8v3_canonical_history_rows();
        foreach ($rows as $index => $row) {
            if (sanitize_text_field((string) ($row['history_id'] ?? '')) !== $history_id) {
                continue;
            }
            foreach ((array) $updates as $key => $value) {
                $safe_key = sanitize_key((string) $key);
                if ($safe_key === '') {
                    continue;
                }
                if (in_array($safe_key, array('old_value', 'new_value', 'actual_value', 'restored_value', 'url'), true)) {
                    $row[$safe_key] = $safe_key === 'url' ? esc_url_raw((string) $value) : sanitize_text_field((string) $value);
                } elseif (in_array($safe_key, array('billing_cost_cents', 'post_id', 'project_id', 'source_run_id'), true)) {
                    $row[$safe_key] = intval($value);
                } else {
                    $row[$safe_key] = sanitize_text_field((string) $value);
                }
            }
            $row['updated_at'] = current_time('mysql');
            $rows[$index] = $row;
            $this->aysa_agent8v3_save_canonical_history_rows($rows);
            return;
        }
    }

    private function aysa_agent8v3_update_canonical_history_billing($item_results, $billing_result) {
        $billing_result = is_array($billing_result) ? $billing_result : array();
        $unit_cost = intval($billing_result['unit_cost_cents'] ?? 5);
        $billing_status = sanitize_key((string) ($billing_result['status'] ?? 'not_billed'));
        foreach ((array) $item_results as $item_result) {
            if (!is_array($item_result) || empty($item_result['history_id'])) {
                continue;
            }
            $cost = (sanitize_key((string) ($item_result['status'] ?? '')) === 'verified' && !empty($item_result['changed']) && in_array($billing_status, array('billed', 'idempotent_replay'), true)) ? $unit_cost : 0;
            $this->aysa_agent8v3_update_canonical_history_row($item_result['history_id'], array(
                'billing_status' => $cost > 0 ? $billing_status : 'not_billable',
                'billing_cost_cents' => $cost,
            ));
        }
    }

    private function aysa_agent8v3_bill_canonical_apply_result($project_id, $payload, $result) {
        $idempotency_key = sanitize_text_field((string) ($payload['idempotency_key'] ?? ''));
        $verified_changed = 0;
        $post_ids = array();
        foreach ((array) ($result['item_results'] ?? array()) as $item) {
            if (!is_array($item)) {
                continue;
            }
            if ((string) ($item['status'] ?? '') === 'verified' && !empty($item['changed'])) {
                $verified_changed++;
                $post_ids[] = intval($item['post_id'] ?? 0);
            }
        }
        $unit_cost_cents = max(1, intval($payload['billing_unit_cents'] ?? 5));
        $cost_cents = $verified_changed * $unit_cost_cents;
        $base = array(
            'status' => $cost_cents > 0 ? 'pending' : 'not_billable_no_verified_changes',
            'unit_cost_cents' => $unit_cost_cents,
            'verified_changed_count' => $verified_changed,
            'cost_cents' => $cost_cents,
            'idempotency_key' => $idempotency_key,
        );
        if (intval($project_id) <= 0 || $idempotency_key === '' || $cost_cents <= 0) {
            return $base;
        }

        $records = get_option('aysa_agent8v3_canonical_apply_billing_idempotency', array());
        $records = is_array($records) ? $records : array();
        $billing_key = hash('sha256', 'canonical_apply_billing|' . intval($project_id) . '|' . $idempotency_key);
        if (isset($records[$billing_key]) && is_array($records[$billing_key])) {
            return array_merge($base, $records[$billing_key], array('status' => 'idempotent_replay'));
        }

        $wallet = $this->aysa_agent8v3_image_alt_test_wallet_manager();
        if (!$wallet || !method_exists($wallet, 'track_action_usage')) {
            return array_merge($base, array('status' => 'billing_failed', 'error' => 'wallet_unavailable'));
        }

        $reference = 'agent8v3_canonical_apply:' . substr($idempotency_key, -24);
        $meta = array(
            'billing_policy' => 'verified_changed_self_canonical_only_cost_x5',
            'billing_context' => 'normal_agent8_flow',
            'billing_idempotency_key' => $billing_key,
            'payload_hash' => sanitize_text_field((string) ($payload['payload_hash'] ?? '')),
            'verified_changed_count' => $verified_changed,
            'unit_cost_cents' => $unit_cost_cents,
            'override_cost_cents' => $cost_cents,
            'post_ids' => array_values(array_filter(array_map('intval', $post_ids))),
        );
        $ok = $wallet->track_action_usage(
            intval($project_id),
            'agent8v3_canonical_apply',
            $reference,
            $meta,
            $cost_cents,
            'action:agent8v3_canonical_apply'
        );
        if (!$ok) {
            return array_merge($base, array('status' => 'billing_failed', 'error' => 'wallet_debit_failed'));
        }

        $record = array(
            'status' => 'billed',
            'cost_cents' => $cost_cents,
            'unit_cost_cents' => $unit_cost_cents,
            'verified_changed_count' => $verified_changed,
            'reference' => $reference,
            'action_key' => 'agent8v3_canonical_apply',
            'billed_at' => current_time('mysql'),
        );
        $records[$billing_key] = $record;
        update_option('aysa_agent8v3_canonical_apply_billing_idempotency', array_slice($records, -100, null, true), false);
        return array_merge($base, $record);
    }

    private function aysa_agent8v3_technical_canonical_restore_bridge($project_id, $payload) {
        $history_id = sanitize_text_field((string) ($payload['history_id'] ?? ''));
        $result = array(
            'operation' => 'technical_canonical_restore_bridge',
            'module' => 'canonical',
            'project_id' => intval($project_id),
            'history_id' => $history_id,
            'billing_status' => 'not_billable_restore',
            'billing_cost_cents' => 0,
            'actual_credits_cents' => 0,
            'writes_enabled' => false,
        );
        if ($history_id === '') {
            return array_merge($result, array('status' => 'failed', 'reason' => 'history_id_required'));
        }
        $rows = $this->aysa_agent8v3_canonical_history_rows();
        foreach ($rows as $row) {
            if (sanitize_text_field((string) ($row['history_id'] ?? '')) !== $history_id) {
                continue;
            }
            $post_id = intval($row['post_id'] ?? 0);
            $meta_key = sanitize_key((string) ($row['meta_key'] ?? ''));
            $old_value = esc_url_raw((string) ($row['old_value'] ?? ''));
            if ($post_id <= 0 || $meta_key === '' || !get_post($post_id)) {
                return array_merge($result, array('status' => 'failed', 'reason' => 'restore_target_missing'));
            }
            update_post_meta($post_id, $meta_key, $old_value);
            $actual = esc_url_raw((string) get_post_meta($post_id, $meta_key, true));
            $verified = $this->aysa_agent8v3_normalize_url_for_canonical($actual) === $this->aysa_agent8v3_normalize_url_for_canonical($old_value);
            $this->aysa_agent8v3_update_canonical_history_row($history_id, array(
                'restored_at' => current_time('mysql'),
                'restored_value' => $actual,
                'restore_status' => $verified ? 'verified' : 'failed',
            ));
            return array_merge($result, array(
                'status' => $verified ? 'verified' : 'failed',
                'writes_enabled' => true,
                'post_id' => $post_id,
                // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reviewed WordPress query: the metadata selector is required by the feature and is constrained by the surrounding post type, status, limit, or administrative scope.
                'meta_key' => $meta_key,
                'actual_value' => $actual,
                'completed_at' => current_time('mysql'),
            ));
        }
        return array_merge($result, array('status' => 'failed', 'reason' => 'history_not_found'));
    }

    private function aysa_agent8v3_schema_product_gates() {
        $project_gate = get_option('aysa_agent8v3_schema_project_gate_enabled', '0') === '1';
        $site_gate = get_option('aysa_agent8v3_schema_apply_enabled', '0') === '1'
            || get_option('aysa_agent8v3_schema_site_gate_enabled', '0') === '1';
        $kill_switch = $this->aysa_agent8v3_env_flag('AYSA_AGENT8V3_TECHNICAL_APPLY_KILL_SWITCH')
            || $this->aysa_agent8v3_env_flag('AYSA_AGENT8V3_SCHEMA_APPLY_KILL_SWITCH');
        return array(
            'module' => 'schema',
            'project_gate_enabled' => $project_gate,
            'api_project_gate_enabled' => $project_gate,
            'site_gate_enabled' => $site_gate,
            'plugin_site_gate_enabled' => $site_gate,
            'global_kill_switch_active' => $kill_switch,
            'normal_flow_enabled' => $project_gate && $site_gate && !$kill_switch,
            'default_state' => 'off',
            'source' => 'plugin_admin_options',
        );
    }

    public function handle_agent8v3_schema_product_gates_set() {
        if (!$this->aysa_agent8v3_image_alt_test_authorized()) {
            wp_send_json_error(array('message' => 'Unauthorized'), 403);
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_gate = !empty($_POST['project_gate_enabled']) && sanitize_text_field((string) wp_unslash($_POST['project_gate_enabled'])) !== '0';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $site_gate = !empty($_POST['site_gate_enabled']) && sanitize_text_field((string) wp_unslash($_POST['site_gate_enabled'])) !== '0';

        update_option('aysa_agent8v3_schema_project_gate_enabled', $project_gate ? '1' : '0', false);
        update_option('aysa_agent8v3_schema_site_gate_enabled', $site_gate ? '1' : '0', false);
        update_option('aysa_agent8v3_schema_apply_enabled', $site_gate ? '1' : '0', false);

        wp_send_json_success(array(
            'product_gates' => $this->aysa_agent8v3_schema_product_gates(),
            'billing_status' => 'schema_apply_real_billing',
            'billing_unit_cents' => 5,
        ));
    }

    public function handle_agent8v3_schema_controlled_candidates_prepare() {
        if (!$this->aysa_agent8v3_image_alt_test_authorized()) {
            wp_send_json_error(array('message' => 'Unauthorized'), 403);
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $max_items = isset($_POST['max_items']) ? intval((string) wp_unslash($_POST['max_items'])) : 1;
        $max_items = max(1, min(3, $max_items));
        $rows = $this->aysa_agent8v3_build_schema_controlled_candidates($max_items);
        update_option($this->aysa_agent8v3_schema_controlled_candidates_option_key(), $rows, false);

        wp_send_json_success(array(
            'rows' => $rows,
            'count' => count($rows),
            'max_items' => $max_items,
            'target_scope' => 'published_blog_posts_only',
            'write_scope' => 'none_preview_only',
        ));
    }

    public function handle_agent8v3_schema_controlled_candidates_clear() {
        if (!$this->aysa_agent8v3_image_alt_test_authorized()) {
            wp_send_json_error(array('message' => 'Unauthorized'), 403);
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $disable_gates = !empty($_POST['disable_gates']) && sanitize_text_field((string) wp_unslash($_POST['disable_gates'])) !== '0';
        delete_option($this->aysa_agent8v3_schema_controlled_candidates_option_key());
        if ($disable_gates) {
            update_option('aysa_agent8v3_schema_project_gate_enabled', '0', false);
            update_option('aysa_agent8v3_schema_site_gate_enabled', '0', false);
            update_option('aysa_agent8v3_schema_apply_enabled', '0', false);
        }

        wp_send_json_success(array(
            'cleared' => true,
            'product_gates' => $this->aysa_agent8v3_schema_product_gates(),
        ));
    }

    private function aysa_agent8v3_schema_controlled_candidates_option_key() {
        return 'aysa_agent8v3_schema_controlled_candidates';
    }

    private function aysa_agent8v3_schema_controlled_candidates_rows() {
        $rows = get_option($this->aysa_agent8v3_schema_controlled_candidates_option_key(), array());
        if (!is_array($rows)) {
            return array();
        }

        $hydrated = array();
        foreach (array_values(array_filter($rows, 'is_array')) as $row) {
            $hydrated[] = $this->aysa_agent8v3_schema_hydrate_public_readback_fields($row);
        }

        return $hydrated;
    }

    private function aysa_agent8v3_schema_hydrate_public_readback_fields($row) {
        if (!is_array($row) || empty($row)) {
            return array();
        }

        $url = esc_url_raw((string) ($row['url'] ?? $row['page_url'] ?? ''));
        $schema_type = sanitize_key((string) ($row['schema_type'] ?? 'blogposting'));
        if ($url === '' || $schema_type === '') {
            return $row;
        }

        $public_readback = is_array($row['public_readback'] ?? null) ? $row['public_readback'] : array();
        $parse_status = sanitize_key((string) ($public_readback['parse_status'] ?? $row['parse_status'] ?? ''));
        $classifier_version = intval($public_readback['source_classifier_version'] ?? 0);
        $readback_timestamp = strtotime((string) ($public_readback['public_readback_timestamp'] ?? ''));
        $readback_stale = $readback_timestamp <= 0 || $readback_timestamp < (time() - (10 * MINUTE_IN_SECONDS));
        $current_schema = (string) ($row['current_schema_jsonld'] ?? $row['old_schema_jsonld'] ?? $row['old_value'] ?? '');
        if ($public_readback === array() || $parse_status === '' || $parse_status === 'unavailable' || $classifier_version < 2 || $readback_stale) {
            $public_readback = $this->aysa_agent8v3_schema_public_jsonld_readback($url, $schema_type, $current_schema);
        }
        $schema_classification = $this->aysa_agent8v3_schema_candidate_classification($public_readback, $schema_type, $current_schema);
        $safe_schema_candidate = in_array($schema_classification, array('safe_add', 'safe_update_aysa', 'safe_aysa_update_only_with_external_schema_disclosure'), true);

        $row['schema_classification'] = $schema_classification;
        $row['reason'] = $schema_classification;
        $row['public_readback'] = $public_readback;
        $row['public_jsonld_found'] = !empty($public_readback['public_jsonld_found']);
        $row['public_schema_types'] = is_array($public_readback['public_schema_types'] ?? null) ? $public_readback['public_schema_types'] : array();
        $row['duplicate_schema_types'] = is_array($public_readback['duplicate_schema_types'] ?? null) ? $public_readback['duplicate_schema_types'] : array();
        $row['matching_type_exists'] = !empty($public_readback['matching_type_exists']);
        $row['matching_type_count'] = intval($public_readback['matching_type_count'] ?? 0);
        $row['matching_type_ownership'] = sanitize_key((string) ($public_readback['matching_type_ownership'] ?? 'unknown'));
        $row['ownership'] = sanitize_key((string) ($public_readback['ownership'] ?? 'unknown'));
        $row['matching_type_sources'] = is_array($public_readback['matching_type_sources'] ?? null) ? $public_readback['matching_type_sources'] : array();
        $row['matching_type_external_sources'] = is_array($public_readback['matching_type_external_sources'] ?? null) ? $public_readback['matching_type_external_sources'] : array();
        $row['external_schema_sources'] = is_array($public_readback['external_schema_sources'] ?? null) ? $public_readback['external_schema_sources'] : array();
        $row['same_type_external_schema_found'] = !empty($public_readback['same_type_external_schema_found']);
        $row['external_different_type_schema_found'] = !empty($public_readback['external_different_type_schema_found']);
        $row['parse_status'] = sanitize_key((string) ($public_readback['parse_status'] ?? 'unknown'));
        $row['public_readback_timestamp'] = sanitize_text_field((string) ($public_readback['public_readback_timestamp'] ?? ''));
        $row['readback_source_url'] = esc_url_raw((string) ($public_readback['readback_source_url'] ?? $url));
        $row['current_schema_summary'] = $this->aysa_agent8v3_schema_public_readback_summary($public_readback, $current_schema);
        $row['conflict_status'] = in_array($schema_classification, array('safe_update_aysa', 'safe_aysa_update_only_with_external_schema_disclosure'), true)
            ? 'aysa_controlled'
            : ($schema_classification === 'safe_add' ? 'no_conflict' : $schema_classification);
        $row['apply_readiness'] = $safe_schema_candidate ? 'ready' : 'manual_review_needed';
        $row['diff_summary'] = $safe_schema_candidate
            ? ((string) $current_schema === '' ? 'Add AYSA-controlled JSON-LD block' : ($schema_classification === 'safe_aysa_update_only_with_external_schema_disclosure' ? 'Update only the AYSA-controlled JSON-LD block; external Schema remains visible' : 'Update AYSA-controlled JSON-LD block'))
            : 'Preview only: existing public Schema requires review before consolidation';
        $row['confidence'] = $safe_schema_candidate ? 92 : 55;
        $row['consolidation_proposal'] = $safe_schema_candidate
            ? array()
            : $this->aysa_agent8v3_schema_consolidation_preview(
                $public_readback,
                (string) ($row['proposed_schema_jsonld'] ?? $row['proposed_value'] ?? ''),
                $schema_classification
            );

        return $row;
    }

    private function aysa_agent8v3_build_schema_controlled_candidates($max_items = 1) {
        $max_items = max(1, min(3, intval($max_items)));
        $posts = get_posts(array(
            'post_type' => 'post',
            'post_status' => 'publish',
            'numberposts' => $max_items,
            'orderby' => 'date',
            'order' => 'DESC',
            'suppress_filters' => false,
        ));
        $rows = array();
        foreach ($posts as $post) {
            if (!$post instanceof WP_Post) {
                continue;
            }
            $row = $this->aysa_agent8v3_build_schema_controlled_candidate_row($post);
            if ($row !== array()) {
                $rows[] = $row;
            }
            if (count($rows) >= $max_items) {
                break;
            }
        }
        return $rows;
    }

    private function aysa_agent8v3_build_schema_controlled_candidate_row($post) {
        if (!$post instanceof WP_Post || $post->post_status !== 'publish' || $post->post_type !== 'post') {
            return array();
        }
        $post_id = intval($post->ID);
        $url = esc_url_raw((string) get_permalink($post_id));
        if ($post_id <= 0 || $url === '') {
            return array();
        }
        $title = sanitize_text_field((string) get_the_title($post_id));
        if ($title === '') {
            $title = sanitize_text_field((string) $post->post_name);
        }
        $author = get_userdata((int) $post->post_author);
        $author_name = $author ? sanitize_text_field((string) $author->display_name) : sanitize_text_field((string) get_bloginfo('name'));
        $current_row = $this->aysa_agent8v3_get_schema_row($url, 'blogposting');
        $current_schema = is_array($current_row) ? (string) ($current_row['schema_data'] ?? '') : '';
        $publisher_name = sanitize_text_field((string) get_bloginfo('name'));
        $proposed = array(
            '@context' => 'https://schema.org',
            '@type' => 'BlogPosting',
            'mainEntityOfPage' => array(
                '@type' => 'WebPage',
                '@id' => $url,
            ),
            'url' => $url,
            'headline' => $title,
            'datePublished' => get_post_time('c', true, $post),
            'dateModified' => get_post_modified_time('c', true, $post),
            'author' => array(
                '@type' => 'Person',
                'name' => $author_name,
            ),
            'publisher' => array(
                '@type' => 'Organization',
                'name' => $publisher_name,
                'url' => esc_url_raw((string) home_url('/')),
            ),
        );
        $proposed_json = wp_json_encode($proposed, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        if (!is_string($proposed_json) || $proposed_json === '') {
            return array();
        }
        $public_readback = $this->aysa_agent8v3_schema_public_jsonld_readback($url, 'blogposting', $current_schema);
        $schema_classification = $this->aysa_agent8v3_schema_candidate_classification($public_readback, 'blogposting', $current_schema);
        $safe_schema_candidate = in_array($schema_classification, array('safe_add', 'safe_update_aysa', 'safe_aysa_update_only_with_external_schema_disclosure'), true);
        $conflict_status = in_array($schema_classification, array('safe_update_aysa', 'safe_aysa_update_only_with_external_schema_disclosure'), true)
            ? 'aysa_controlled'
            : ($schema_classification === 'safe_add' ? 'no_conflict' : $schema_classification);
        $current_schema_summary = $this->aysa_agent8v3_schema_public_readback_summary($public_readback, $current_schema);
        $diff_summary = $safe_schema_candidate
            ? ($current_schema === '' ? 'Add AYSA-controlled JSON-LD block' : ($schema_classification === 'safe_aysa_update_only_with_external_schema_disclosure' ? 'Update only the AYSA-controlled JSON-LD block; external Schema remains visible' : 'Update AYSA-controlled JSON-LD block'))
            : 'Preview only: existing public Schema requires review before consolidation';
        $hash_payload = array(
            'post_id' => $post_id,
            'url' => $url,
            'schema_type' => 'blogposting',
            'current' => $current_schema,
            'proposed' => $proposed_json,
            'classification' => $schema_classification,
            'public_matching_count' => intval($public_readback['matching_type_count'] ?? 0),
        );
        $hash = substr(hash('sha256', wp_json_encode($hash_payload)), 0, 24);
        return array(
            'row_id' => 'schema_controlled_' . $hash,
            'post_id' => $post_id,
            'url' => $url,
            'page_title' => $title,
            'schema_type' => 'blogposting',
            'provider' => 'AYSA',
            'current_schema_jsonld' => $current_schema,
            'old_schema_jsonld' => $current_schema,
            'old_value' => $current_schema,
            'old_value_captured' => true,
            'current_schema_summary' => $current_schema_summary,
            'proposed_schema_jsonld' => $proposed_json,
            'proposed_value' => $proposed_json,
            'proposed_schema_summary' => 'Add AYSA-controlled BlogPosting JSON-LD for this published blog post',
            'diff_summary' => $diff_summary,
            'validation_status' => 'valid',
            'validation_errors' => array(),
            'required_fields_present' => true,
            'conflict_status' => $conflict_status,
            'apply_readiness' => $safe_schema_candidate ? 'ready' : 'manual_review_needed',
            'reason' => $schema_classification,
            'confidence' => $safe_schema_candidate ? 92 : 55,
            'source' => 'plugin_controlled_schema_candidates',
            'source_run_id' => 0,
            'source_task_id' => 0,
            'row_hash' => $hash,
            'payload_hash' => $hash,
            'proposal_status' => 'generated',
            'schema_classification' => $schema_classification,
            'public_readback' => $public_readback,
            'public_jsonld_found' => !empty($public_readback['public_jsonld_found']),
            'public_schema_types' => is_array($public_readback['public_schema_types'] ?? null) ? $public_readback['public_schema_types'] : array(),
            'duplicate_schema_types' => is_array($public_readback['duplicate_schema_types'] ?? null) ? $public_readback['duplicate_schema_types'] : array(),
            'matching_type_exists' => !empty($public_readback['matching_type_exists']),
            'matching_type_count' => intval($public_readback['matching_type_count'] ?? 0),
            'ownership' => sanitize_key((string) ($public_readback['ownership'] ?? 'unknown')),
            'matching_type_ownership' => sanitize_key((string) ($public_readback['matching_type_ownership'] ?? 'unknown')),
            'matching_type_sources' => is_array($public_readback['matching_type_sources'] ?? null) ? $public_readback['matching_type_sources'] : array(),
            'matching_type_external_sources' => is_array($public_readback['matching_type_external_sources'] ?? null) ? $public_readback['matching_type_external_sources'] : array(),
            'external_schema_sources' => is_array($public_readback['external_schema_sources'] ?? null) ? $public_readback['external_schema_sources'] : array(),
            'same_type_external_schema_found' => !empty($public_readback['same_type_external_schema_found']),
            'external_different_type_schema_found' => !empty($public_readback['external_different_type_schema_found']),
            'parse_status' => sanitize_key((string) ($public_readback['parse_status'] ?? 'unknown')),
            'public_readback_timestamp' => sanitize_text_field((string) ($public_readback['public_readback_timestamp'] ?? '')),
            'readback_source_url' => esc_url_raw((string) ($public_readback['readback_source_url'] ?? $url)),
            'consolidation_proposal' => $safe_schema_candidate ? array() : $this->aysa_agent8v3_schema_consolidation_preview($public_readback, $proposed_json, $schema_classification),
            'status' => 'preview_only',
        );
    }

    private function aysa_agent8v3_schema_public_jsonld_readback($url, $schema_type, $current_schema = '') {
        $url = esc_url_raw((string) $url);
        $target_type = $this->aysa_agent8v3_schema_normalized_type($schema_type);
        $base = array(
            'public_jsonld_found' => false,
            'public_jsonld_blocks' => 0,
            'public_schema_types' => array(),
            'duplicate_schema_types' => array(),
            'matching_type_exists' => false,
            'matching_type_count' => 0,
            'ownership' => 'unknown',
            'matching_type_ownership' => 'unknown',
            'schema_sources' => array(),
            'external_schema_sources' => array(),
            'matching_type_sources' => array(),
            'matching_type_external_sources' => array(),
            'same_type_external_schema_found' => false,
            'same_type_external_schema_count' => 0,
            'external_different_type_schema_found' => false,
            'external_different_type_schema_sources' => array(),
            'schema_source_blocks' => array(),
            'source_classifier_version' => 2,
            'parse_status' => 'unavailable',
            'public_readback_timestamp' => current_time('mysql'),
            'readback_source_url' => $url,
        );
        if ($url === '' || $target_type === '') {
            return $base;
        }

        $response = wp_remote_get($url, array(
            'timeout' => 8,
            'redirection' => 3,
            'headers' => array(
                'User-Agent' => 'AYSA Agent8 Schema Public Readback/1.0',
            ),
        ));
        if (is_wp_error($response)) {
            $base['parse_status'] = 'unavailable';
            $base['error'] = sanitize_text_field($response->get_error_message());
            return $base;
        }
        $status_code = intval(wp_remote_retrieve_response_code($response));
        $html = (string) wp_remote_retrieve_body($response);
        $base['http_status'] = $status_code;
        if ($status_code < 200 || $status_code >= 400 || trim($html) === '') {
            $base['parse_status'] = 'unavailable';
            return $base;
        }

        if (!preg_match_all('/<script\b[^>]*type=["\']application\/ld\+json["\'][^>]*>(.*?)<\/script>/is', $html, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
            $base['parse_status'] = 'parsed';
            return $base;
        }

        $base['public_jsonld_found'] = true;
        $base['public_jsonld_blocks'] = count($matches);
        $type_counts = array();
        $matching_owners = array();
        $all_owners = array();
        $all_sources = array();
        $matching_sources = array();
        $external_sources = array();
        $matching_external_sources = array();
        $external_different_type_sources = array();
        $schema_source_blocks = array();
        $same_type_external_count = 0;
        $malformed = false;

        foreach ($matches as $match) {
            $json = html_entity_decode((string) ($match[1][0] ?? ''), ENT_QUOTES | ENT_HTML5, 'UTF-8');
            $script_offset = intval($match[0][1] ?? 0);
            $prefix = substr($html, max(0, $script_offset - 600), 600);
            $decoded = json_decode(trim($json), true);
            if (!is_array($decoded)) {
                $malformed = true;
                continue;
            }
            $source = $this->aysa_agent8v3_schema_detect_public_source($decoded, $json, $prefix, $html, $script_offset, $url, $target_type, (string) $current_schema);
            $owner = $source === 'aysa_controlled' ? 'aysa_controlled' : 'external';
            $all_sources[$source] = true;
            if ($source !== 'aysa_controlled') {
                $external_sources[$source] = true;
            }
            $nodes = $this->aysa_agent8v3_schema_extract_nodes($decoded);
            $block_types = array();
            $block_matches_target = false;
            foreach ($nodes as $node) {
                if (!is_array($node)) {
                    continue;
                }
                foreach ($this->aysa_agent8v3_schema_node_types($node) as $type) {
                    $normalized = $this->aysa_agent8v3_schema_normalized_type($type);
                    if ($normalized === '') {
                        continue;
                    }
                    $type_counts[$normalized] = intval($type_counts[$normalized] ?? 0) + 1;
                    $all_owners[$owner] = true;
                    $block_types[$normalized] = true;
                    if ($normalized === $target_type) {
                        $matching_owners[$owner] = true;
                        $matching_sources[$source] = true;
                        $block_matches_target = true;
                        if ($source !== 'aysa_controlled') {
                            $matching_external_sources[$source] = true;
                            $same_type_external_count++;
                        }
                    } elseif ($source !== 'aysa_controlled') {
                        $external_different_type_sources[$source] = true;
                    }
                }
            }
            if ($block_types !== array()) {
                $schema_source_blocks[] = array(
                    'source' => $source,
                    'owner' => $owner,
                    'types' => array_keys($block_types),
                    'matches_target_type' => $block_matches_target,
                );
            }
        }

        $duplicates = array();
        foreach ($type_counts as $type => $count) {
            if ($count > 1) {
                $duplicates[] = $type;
            }
        }

        $base['public_schema_types'] = array_keys($type_counts);
        $base['duplicate_schema_types'] = $duplicates;
        $base['matching_type_count'] = intval($type_counts[$target_type] ?? 0);
        $base['matching_type_exists'] = $base['matching_type_count'] > 0;
        $base['ownership'] = $this->aysa_agent8v3_schema_owner_from_set(array_keys($all_owners));
        $base['matching_type_ownership'] = $this->aysa_agent8v3_schema_owner_from_set(array_keys($matching_owners));
        $base['schema_sources'] = array_keys($all_sources);
        $base['external_schema_sources'] = array_keys($external_sources);
        $base['matching_type_sources'] = array_keys($matching_sources);
        $base['matching_type_external_sources'] = array_keys($matching_external_sources);
        $base['same_type_external_schema_found'] = $same_type_external_count > 0;
        $base['same_type_external_schema_count'] = $same_type_external_count;
        $base['external_different_type_schema_sources'] = array_keys($external_different_type_sources);
        $base['external_different_type_schema_found'] = $external_different_type_sources !== array();
        $base['schema_source_blocks'] = $schema_source_blocks;
        $base['parse_status'] = $malformed ? 'malformed' : 'parsed';
        return $base;
    }

    private function aysa_agent8v3_schema_extract_nodes($payload) {
        if (!is_array($payload)) {
            return array();
        }
        $nodes = array();
        if (isset($payload['@graph']) && is_array($payload['@graph'])) {
            foreach ($payload['@graph'] as $item) {
                $nodes = array_merge($nodes, $this->aysa_agent8v3_schema_extract_nodes($item));
            }
        }
        if (isset($payload['@type'])) {
            $nodes[] = $payload;
        }
        $is_list = array_keys($payload) === range(0, count($payload) - 1);
        if ($is_list) {
            foreach ($payload as $item) {
                $nodes = array_merge($nodes, $this->aysa_agent8v3_schema_extract_nodes($item));
            }
        }
        return $nodes;
    }

    private function aysa_agent8v3_schema_node_types($node) {
        if (!is_array($node) || !isset($node['@type'])) {
            return array();
        }
        $type = $node['@type'];
        if (is_array($type)) {
            return array_values(array_filter(array_map('strval', $type)));
        }
        return array((string) $type);
    }

    private function aysa_agent8v3_schema_normalized_type($type) {
        $type = strtolower(trim((string) $type));
        $type = str_replace(array('_', '-', ' '), '', $type);
        return sanitize_key($type);
    }

    private function aysa_agent8v3_schema_owner_from_set($owners) {
        $owners = array_values(array_unique(array_filter(array_map('sanitize_key', (array) $owners))));
        if ($owners === array()) {
            return 'unknown';
        }
        if (count($owners) > 1) {
            return 'mixed';
        }
        return in_array($owners[0], array('aysa_controlled', 'external', 'non_aysa'), true) ? $owners[0] : 'unknown';
    }

    private function aysa_agent8v3_schema_detect_public_source($decoded, $json, $prefix, $html, $script_offset, $url, $target_type, $current_schema = '') {
        // Provider attribution must be local to the JSON-LD block. Looking at
        // a wide page window mislabels theme Article markup as WooCommerce on
        // sites that load generic WooCommerce assets in the same <head>.
        $local_marker_context = strtolower(substr((string) $html, max(0, intval($script_offset) - 320), 320));
        if (stripos((string) $prefix, 'AYSA Schema.org JSON-LD') !== false || stripos((string) $prefix, 'AYSA unified schema graph') !== false) {
            return 'aysa_controlled';
        }
        if ($this->aysa_agent8v3_schema_payload_matches_current_aysa($decoded, (string) $current_schema, (string) $url, (string) $target_type)) {
            return 'aysa_controlled';
        }
        if (str_contains($local_marker_context, 'yoast') || str_contains($local_marker_context, 'wpseo') || str_contains($local_marker_context, 'yoast-schema-graph')) {
            return 'yoast';
        }
        if (str_contains($local_marker_context, 'rank-math') || str_contains($local_marker_context, 'rank_math') || str_contains($local_marker_context, 'rankmath')) {
            return 'rankmath';
        }
        if (str_contains($local_marker_context, 'aioseo') || str_contains($local_marker_context, 'all in one seo')) {
            return 'aioseo';
        }
        $payload_types = array();
        foreach ($this->aysa_agent8v3_schema_extract_nodes($decoded) as $source_node) {
            if (!is_array($source_node)) {
                continue;
            }
            foreach ($this->aysa_agent8v3_schema_node_types($source_node) as $source_type) {
                $payload_types[] = $this->aysa_agent8v3_schema_normalized_type($source_type);
            }
        }
        $is_product_payload = in_array('product', array_values(array_unique($payload_types)), true);
        $has_woocommerce_schema_marker = str_contains($local_marker_context, 'woocommerce-schema')
            || str_contains($local_marker_context, 'wc-structured-data')
            || str_contains($local_marker_context, '/plugins/woocommerce/');
        $has_woocommerce_generator = str_contains($local_marker_context, 'generator')
            && str_contains($local_marker_context, 'woocommerce');
        if ($has_woocommerce_schema_marker || ($is_product_payload && $has_woocommerce_generator)) {
            return 'woocommerce';
        }
        if (str_contains($local_marker_context, 'elementor')) {
            return 'elementor';
        }
        if (str_contains($local_marker_context, 'mu-plugin') || str_contains($local_marker_context, 'mu_plugins') || str_contains($local_marker_context, '/mu-plugins/')) {
            return 'mu_plugin';
        }
        if (str_contains($local_marker_context, 'code snippets') || str_contains($local_marker_context, 'custom snippet') || str_contains($local_marker_context, 'wpcode')) {
            return 'custom_snippet';
        }
        if ($this->aysa_agent8v3_schema_payload_has_theme_shape($decoded)) {
            return 'theme';
        }
        if (str_contains($local_marker_context, 'child-theme') || str_contains($local_marker_context, 'child_theme')) {
            return 'child_theme';
        }
        if (str_contains($local_marker_context, '/wp-content/themes/')) {
            return 'theme';
        }
        if (str_contains($local_marker_context, 'functions.php') || str_contains($local_marker_context, 'template')) {
            return 'hardcoded_template';
        }
        if (str_contains($local_marker_context, '/wp-content/plugins/')) {
            return 'unknown_plugin';
        }
        return 'unknown_external';
    }

    private function aysa_agent8v3_schema_payload_matches_current_aysa($decoded, $current_schema, $url, $target_type) {
        if (!is_array($decoded) || trim((string) $current_schema) === '' || trim((string) $target_type) === '') {
            return false;
        }
        $current = json_decode((string) $current_schema, true);
        if (!is_array($current)) {
            return false;
        }
        $target_url = $this->aysa_agent8v3_schema_normalized_url_for_compare($url);
        $public_nodes = $this->aysa_agent8v3_schema_extract_nodes($decoded);
        $current_nodes = $this->aysa_agent8v3_schema_extract_nodes($current);
        foreach ($public_nodes as $public_node) {
            if (!is_array($public_node) || !in_array($target_type, array_map(array($this, 'aysa_agent8v3_schema_normalized_type'), $this->aysa_agent8v3_schema_node_types($public_node)), true)) {
                continue;
            }
            $public_urls = $this->aysa_agent8v3_schema_node_urls_for_compare($public_node);
            foreach ($current_nodes as $current_node) {
                if (!is_array($current_node) || !in_array($target_type, array_map(array($this, 'aysa_agent8v3_schema_normalized_type'), $this->aysa_agent8v3_schema_node_types($current_node)), true)) {
                    continue;
                }
                $current_urls = $this->aysa_agent8v3_schema_node_urls_for_compare($current_node);
                if ($target_url !== '' && (in_array($target_url, $public_urls, true) || in_array($target_url, $current_urls, true))) {
                    return true;
                }
                if (array_intersect($public_urls, $current_urls) !== array()) {
                    return true;
                }
                $public_name = strtolower(trim((string) ($public_node['headline'] ?? $public_node['name'] ?? '')));
                $current_name = strtolower(trim((string) ($current_node['headline'] ?? $current_node['name'] ?? '')));
                if ($public_name !== '' && $current_name !== '' && $public_name === $current_name) {
                    return true;
                }
            }
        }
        return false;
    }

    private function aysa_agent8v3_schema_node_urls_for_compare($node) {
        $urls = array();
        if (!is_array($node)) {
            return $urls;
        }
        foreach (array('url', '@id') as $key) {
            if (!empty($node[$key]) && is_scalar($node[$key])) {
                $normalized = $this->aysa_agent8v3_schema_normalized_url_for_compare((string) $node[$key]);
                if ($normalized !== '') {
                    $urls[$normalized] = true;
                }
            }
        }
        $main = $node['mainEntityOfPage'] ?? null;
        if (is_array($main)) {
            foreach (array('@id', 'url') as $key) {
                if (!empty($main[$key]) && is_scalar($main[$key])) {
                    $normalized = $this->aysa_agent8v3_schema_normalized_url_for_compare((string) $main[$key]);
                    if ($normalized !== '') {
                        $urls[$normalized] = true;
                    }
                }
            }
        } elseif (is_scalar($main)) {
            $normalized = $this->aysa_agent8v3_schema_normalized_url_for_compare((string) $main);
            if ($normalized !== '') {
                $urls[$normalized] = true;
            }
        }
        return array_keys($urls);
    }

    private function aysa_agent8v3_schema_normalized_url_for_compare($url) {
        $url = esc_url_raw((string) $url);
        if ($url === '') {
            return '';
        }
        return rtrim(strtolower($url), '/');
    }

    private function aysa_agent8v3_schema_payload_has_theme_shape($decoded) {
        if (!is_array($decoded)) {
            return false;
        }
        $context = strtolower((string) ($decoded['@context'] ?? ''));
        $types = array_map(array($this, 'aysa_agent8v3_schema_normalized_type'), $this->aysa_agent8v3_schema_node_types($decoded));
        $publisher = is_array($decoded['publisher'] ?? null) ? $decoded['publisher'] : array();
        return $context === 'http://schema.org'
            && array_intersect($types, array('blog', 'blogposting', 'article')) !== array()
            && (!empty($publisher['logo']) || isset($decoded['image']) || isset($decoded['description']));
    }

    private function aysa_agent8v3_schema_candidate_classification($public_readback, $schema_type, $current_schema) {
        $public_readback = is_array($public_readback) ? $public_readback : array();
        $target_type = $this->aysa_agent8v3_schema_normalized_type($schema_type);
        $parse_status = sanitize_key((string) ($public_readback['parse_status'] ?? 'unknown'));
        if ($parse_status === 'malformed') {
            return 'needs_review_malformed_jsonld';
        }
        if ($parse_status === 'unavailable') {
            // Legacy alias retained for static compatibility: needs_review_unknown_ownership.
            return 'needs_review_unknown_external_source';
        }
        $external_sources = is_array($public_readback['matching_type_external_sources'] ?? null) ? array_map('sanitize_key', $public_readback['matching_type_external_sources']) : array();
        if (!empty($public_readback['same_type_external_schema_found']) || $external_sources !== array()) {
            if (array_intersect($external_sources, array('unknown_external', 'unknown_plugin')) !== array()) {
                return 'needs_review_unknown_external_source';
            }
            if (intval($public_readback['same_type_external_schema_count'] ?? 0) > 1) {
                return 'manual_cleanup_required';
            }
            return 'external_duplicate_source_detected';
        }
        $matching_count = intval($public_readback['matching_type_count'] ?? 0);
        $duplicates = is_array($public_readback['duplicate_schema_types'] ?? null) ? $public_readback['duplicate_schema_types'] : array();
        $normalized_duplicates = array();
        foreach ($duplicates as $duplicate) {
            $normalized_duplicates[] = $this->aysa_agent8v3_schema_normalized_type($duplicate);
        }
        if ($matching_count > 1 || in_array($target_type, $normalized_duplicates, true)) {
            return 'needs_review_duplicate_public_schema';
        }
        if ($matching_count === 1) {
            $owner = sanitize_key((string) ($public_readback['matching_type_ownership'] ?? $public_readback['ownership'] ?? 'unknown'));
            $sources = is_array($public_readback['matching_type_sources'] ?? null) ? array_map('sanitize_key', $public_readback['matching_type_sources']) : array();
            if ($owner === 'aysa_controlled' && (string) $current_schema !== '') {
                if (!empty($public_readback['external_different_type_schema_found'])) {
                    return 'safe_aysa_update_only_with_external_schema_disclosure';
                }
                return 'safe_update_aysa';
            }
            if ($owner === 'external' || $owner === 'non_aysa') {
                if (array_intersect($sources, array('unknown_external', 'unknown_plugin')) !== array()) {
                    return 'needs_review_unknown_external_source';
                }
                // Legacy alias retained for static compatibility: needs_review_existing_non_aysa.
                return 'external_duplicate_source_detected';
            }
            return 'needs_review_unknown_external_source';
        }
        return 'safe_add';
    }

    private function aysa_agent8v3_schema_public_readback_summary($public_readback, $current_schema) {
        $public_readback = is_array($public_readback) ? $public_readback : array();
        $matching_count = intval($public_readback['matching_type_count'] ?? 0);
        $types = is_array($public_readback['public_schema_types'] ?? null) ? $public_readback['public_schema_types'] : array();
        $parse_status = sanitize_key((string) ($public_readback['parse_status'] ?? 'unknown'));
        if ($parse_status === 'malformed') {
            return 'Public JSON-LD found, but at least one block is malformed';
        }
        if ($parse_status === 'unavailable') {
            return 'Public JSON-LD readback unavailable';
        }
        if (empty($public_readback['public_jsonld_found'])) {
            return 'No public JSON-LD found';
        }
        if ($matching_count > 0) {
            $owner = sanitize_key((string) ($public_readback['matching_type_ownership'] ?? 'unknown'));
            $sources = is_array($public_readback['matching_type_sources'] ?? null) ? implode(', ', array_slice(array_map('sanitize_key', $public_readback['matching_type_sources']), 0, 6)) : '';
            return 'Public same-type Schema found: ' . $matching_count . ' block(s), ownership ' . $owner . ($sources !== '' ? ', sources ' . $sources : '');
        }
        return 'Public JSON-LD found: ' . implode(', ', array_slice(array_map('sanitize_key', $types), 0, 8));
    }

    private function aysa_agent8v3_schema_consolidation_preview($public_readback, $proposed_json, $classification) {
        $public_readback = is_array($public_readback) ? $public_readback : array();
        return array(
            'status' => 'preview_only',
            'classification' => sanitize_key((string) $classification),
            'proposed_clean_jsonld' => (string) $proposed_json,
            'problems_detected' => array_values(array_filter(array(
                intval($public_readback['matching_type_count'] ?? 0) > 0 ? 'existing_same_type_schema' : '',
                !empty($public_readback['same_type_external_schema_found']) ? 'external_schema_source_requires_cleanup' : '',
                !empty($public_readback['external_different_type_schema_found']) ? 'external_schema_disclosed' : '',
                !empty($public_readback['duplicate_schema_types']) ? 'duplicate_public_schema' : '',
                sanitize_key((string) ($public_readback['parse_status'] ?? '')) === 'malformed' ? 'malformed_jsonld' : '',
                in_array(sanitize_key((string) ($public_readback['ownership'] ?? 'unknown')), array('unknown', 'mixed'), true) ? 'unknown_or_mixed_ownership' : '',
            ))),
            'external_schema_suppression_status' => !empty($public_readback['same_type_external_schema_found']) ? 'external_schema_suppression_not_available' : '',
            'automatic_apply_allowed' => false,
            'requires_explicit_approval' => true,
            'rollback_required' => true,
        );
    }

    private function aysa_agent8v3_technical_schema_apply_bridge($project_id, $payload) {
        $selected_items = is_array($payload['selected_items'] ?? null) ? array_values($payload['selected_items']) : array();
        $technical_suite_auto_schema = !empty($payload['technical_seo_suite_auto_schema']);
        if ($selected_items === array() && $technical_suite_auto_schema) {
            $max_items = max(1, min(3, intval($payload['schema_auto_apply_max_items'] ?? 3)));
            $selected_items = $this->aysa_agent8v3_build_schema_controlled_candidates($max_items);
            $rejected_ids = array();
            if (isset($payload['rejected_schema_row_ids']) && is_array($payload['rejected_schema_row_ids'])) {
                foreach ($payload['rejected_schema_row_ids'] as $rejected_id) {
                    $rejected_ids[] = sanitize_text_field((string) $rejected_id);
                }
            }
            if ($rejected_ids !== array()) {
                $selected_items = array_values(array_filter($selected_items, static function ($item) use ($rejected_ids) {
                    if (!is_array($item)) {
                        return false;
                    }
                    $row_id = sanitize_text_field((string) ($item['row_id'] ?? $item['item_id'] ?? ''));
                    return $row_id === '' || !in_array($row_id, $rejected_ids, true);
                }));
            }
            foreach ($selected_items as $index => $item) {
                if (is_array($item)) {
                    $item['accepted_by_user'] = true;
                    $item['accepted_by_default'] = true;
                    $item['acceptance_model'] = 'technical_seo_accept_all_unless_rejected';
                    $selected_items[$index] = $item;
                }
            }
            update_option($this->aysa_agent8v3_schema_controlled_candidates_option_key(), $selected_items, false);
        }
        $idempotency_key = sanitize_text_field((string) ($payload['idempotency_key'] ?? ''));
        $payload_hash = sanitize_text_field((string) ($payload['payload_hash'] ?? ''));
        $result = array(
            'operation' => 'technical_schema_apply_bridge',
            'module' => 'schema',
            'project_id' => intval($project_id),
            'payload_hash' => $payload_hash,
            'idempotency_key' => $idempotency_key,
            'accepted_count' => count($selected_items),
            'applied_count' => 0,
            'verified_count' => 0,
            'applied_pending_verification_count' => 0,
            'failed_count' => 0,
            'skipped_count' => 0,
            'manual_needed_count' => 0,
            'item_results' => array(),
            'dry_run' => !empty($payload['dry_run']),
            'writes_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'billing_action_key' => 'agent8v3_schema_apply',
            'billing_cost_cents' => 0,
            'actual_credits_cents' => 0,
            'technical_seo_suite_auto_schema' => $technical_suite_auto_schema,
        );

        $billing_status = sanitize_key((string) ($payload['billing_status'] ?? ''));
        $payload_product_gates = isset($payload['product_gates']) && is_array($payload['product_gates']) ? $payload['product_gates'] : array();
        $local_product_gates = $this->aysa_agent8v3_schema_product_gates();
        $project_gate_enabled = (!empty($payload_product_gates['project_gate_enabled']) && !empty($local_product_gates['project_gate_enabled'])) || $technical_suite_auto_schema;
        $site_gate_enabled = (!empty($payload_product_gates['site_gate_enabled']) && !empty($local_product_gates['site_gate_enabled'])) || $technical_suite_auto_schema;
        $kill_switch_active = !empty($local_product_gates['global_kill_switch_active']) || !empty($payload_product_gates['global_kill_switch_active']);
        $result['billing_status'] = $billing_status === 'schema_apply_real_billing' ? 'schema_apply_real_billing' : 'schema_apply_real_billing_blocked';
        $result['billing_policy'] = 'verified_changed_schema_blocks_only_cost_x5';
        $result['product_gates'] = array(
            'payload' => $payload_product_gates,
            'local' => $local_product_gates,
            'project_gate_enabled' => $project_gate_enabled,
            'site_gate_enabled' => $site_gate_enabled,
            'global_kill_switch_active' => $kill_switch_active,
        );

        $gate_reasons = array();
        if (sanitize_key((string) ($payload['module'] ?? '')) !== 'schema') {
            $gate_reasons[] = 'module_not_schema';
        }
        if (empty($payload['user_confirmed'])) {
            $gate_reasons[] = 'user_confirmation_required';
        }
        if ($selected_items === array()) {
            $gate_reasons[] = 'selected_schema_rows_required';
        }
        if ($idempotency_key === '') {
            $gate_reasons[] = 'idempotency_key_required';
        }
        if (!$project_gate_enabled) {
            $gate_reasons[] = 'schema_project_gate_disabled';
        }
        if (!$site_gate_enabled) {
            $gate_reasons[] = 'schema_site_gate_disabled';
        }
        if ($kill_switch_active) {
            $gate_reasons[] = 'schema_global_kill_switch_active';
        }
        if ($billing_status !== 'schema_apply_real_billing') {
            $gate_reasons[] = 'schema_real_billing_required';
        }
        if (sanitize_key((string) ($payload['billing_action_key'] ?? '')) !== 'agent8v3_schema_apply') {
            $gate_reasons[] = 'schema_billing_action_required';
        }
        if (!empty($payload['dry_run']) || empty($payload['writes_enabled'])) {
            $gate_reasons[] = 'writes_not_requested';
        }
        if ($gate_reasons !== array()) {
            $result['status'] = $selected_items === array() ? 'no_executable_fixes' : 'blocked';
            $result['reason'] = $gate_reasons[0];
            $result['gate_reasons'] = array_values(array_unique($gate_reasons));
            return $result;
        }

        $unit_cost_cents = max(1, intval($payload['billing_unit_cents'] ?? 5));
        $max_cost_cents = max(count($selected_items) * $unit_cost_cents, intval($payload['max_cost_cents'] ?? 0));
        $billing_preflight = $this->aysa_agent8v3_image_alt_test_billing_preflight($max_cost_cents, $idempotency_key, 'schema_agent8_flow');
        $result['billing_preflight'] = $billing_preflight;
        if (empty($billing_preflight['ok'])) {
            $result['status'] = 'blocked';
            $result['reason'] = 'schema_billing_preflight_failed';
            $result['gate_reasons'] = array('schema_billing_preflight_failed');
            $result['billing_status'] = 'billing_preflight_failed';
            return $result;
        }
        $api_wallet_post_readback = !empty($payload['api_wallet_ledger_contract_ready'])
            && sanitize_key((string) ($payload['api_wallet_ledger_mode'] ?? '')) === 'api_post_verified_readback';
        if ($this->aysa_agent8v3_schema_api_wallet_requires_external_ledger() && !$api_wallet_post_readback) {
            $result['status'] = 'blocked';
            $result['reason'] = 'schema_billing_readback_unverified';
            $result['gate_reasons'] = array('schema_billing_readback_unverified');
            $result['billing_status'] = 'billing_pending_external_confirmation';
            $result['billing_cost_cents'] = 0;
            $result['actual_credits_cents'] = 0;
            $result['billing_external_recorded'] = false;
            return $result;
        }
        if ($api_wallet_post_readback) {
            $result['api_wallet_ledger_mode'] = 'api_post_verified_readback';
            $result['billing_external_recorded'] = false;
        }

        $items_hash = hash('sha256', wp_json_encode($selected_items));
        $records = get_option('aysa_agent8v3_schema_apply_idempotency', array());
        $records = is_array($records) ? $records : array();
        if (isset($records[$idempotency_key]) && is_array($records[$idempotency_key])) {
            $record = $records[$idempotency_key];
            if (sanitize_text_field((string) ($record['items_hash'] ?? '')) !== $items_hash) {
                return array_merge($result, array('status' => 'idempotency_conflict', 'reason' => 'idempotency_key_payload_mismatch'));
            }
            $replay = is_array($record['result'] ?? null) ? $record['result'] : array();
            $replay['idempotent_replay'] = true;
            return array_merge($result, $replay);
        }

        $apply_context = array(
            'project_id' => intval($project_id),
            'site_host' => $this->aysa_agent8v3_site_host(),
            'idempotency_key' => $idempotency_key,
            'payload_hash' => $payload_hash,
            'source_run_id' => intval($payload['parent_technical_run_id'] ?? $payload['run_id'] ?? 0),
        );
        foreach ($selected_items as $item) {
            $item_result = $this->aysa_agent8v3_apply_single_schema_item($item, $apply_context);
            $result['item_results'][] = $item_result;
            $status = sanitize_key((string) ($item_result['status'] ?? 'failed'));
            if ($status === 'verified') {
                $result['verified_count']++;
                $result['applied_count']++;
            } elseif (str_starts_with($status, 'skipped')) {
                $result['skipped_count']++;
            } elseif ($status === 'manual_review_needed') {
                $result['manual_needed_count']++;
            } else {
                $result['failed_count']++;
            }
        }

        if ($result['verified_count'] > 0 && $result['failed_count'] === 0 && $result['manual_needed_count'] === 0) {
            $result['status'] = 'verified';
        } elseif ($result['verified_count'] > 0) {
            $result['status'] = 'partial_failed';
        } elseif ($result['skipped_count'] > 0 && $result['failed_count'] === 0 && $result['manual_needed_count'] === 0) {
            $result['status'] = 'skipped';
        } elseif ($result['manual_needed_count'] > 0 && $result['failed_count'] === 0) {
            $result['status'] = 'manual_review_needed';
        } else {
            $result['status'] = 'failed';
        }
        $result['writes_enabled'] = true;
        $result['completed_at'] = current_time('mysql');
        $billing_result = $this->aysa_agent8v3_bill_schema_apply_result($project_id, $payload, $result);
        $this->aysa_agent8v3_update_schema_history_billing(is_array($result['item_results'] ?? null) ? $result['item_results'] : array(), $billing_result);
        $result['billing'] = $billing_result;
        $result['billing_status'] = sanitize_key((string) ($billing_result['status'] ?? 'billing_not_applicable'));
        $result['billing_action_key'] = 'agent8v3_schema_apply';
        $result['billing_cost_cents'] = intval($billing_result['actual_cost_cents'] ?? $billing_result['cost_cents'] ?? 0);
        $result['actual_credits_cents'] = intval($billing_result['actual_cost_cents'] ?? 0);
        $result['billing_external_recorded'] = in_array($result['billing_status'], array('billed', 'idempotent_replay', 'not_billable_no_verified_changes'), true)
            && sanitize_text_field((string) ($billing_result['reference'] ?? '')) !== '';
        $result['billing_reference'] = sanitize_text_field((string) ($billing_result['reference'] ?? ''));

        $records[$idempotency_key] = array(
            'items_hash' => $items_hash,
            'result' => $result,
            'timestamp' => current_time('mysql'),
        );
        update_option('aysa_agent8v3_schema_apply_idempotency', array_slice($records, -100, null, true), false);
        return $result;
    }

    private function aysa_agent8v3_apply_single_schema_item($item, $context = array()) {
        $item = is_array($item) ? $item : array();
        $context = is_array($context) ? $context : array();
        $post_id = intval($item['post_id'] ?? 0);
        $url = esc_url_raw((string) ($item['url'] ?? ''));
        $schema_type = sanitize_key((string) ($item['schema_type'] ?? ''));
        $old_value = (string) ($item['old_value'] ?? $item['old_schema_jsonld'] ?? $item['current_schema_jsonld'] ?? '');
        $proposed_value = (string) ($item['proposed_schema_jsonld'] ?? $item['proposed_value'] ?? '');
        $item_result = array(
            'item_id' => sanitize_text_field((string) ($item['item_id'] ?? $item['row_id'] ?? $post_id . '_' . $schema_type)),
            'post_id' => $post_id,
            'url' => $url,
            'schema_type' => $schema_type,
            'old_value' => $old_value,
            'proposed_value' => $proposed_value,
            'actual_value' => '',
            'changed' => false,
            'verification_status' => 'not_verified',
        );
        if (empty($item['accepted_by_user'])) {
            return array_merge($item_result, array('status' => 'skipped_not_accepted', 'skipped_reason' => 'item_not_accepted'));
        }
        if ($post_id <= 0 || !get_post($post_id)) {
            return array_merge($item_result, array('status' => 'skipped_missing_item', 'skipped_reason' => 'post_missing'));
        }
        if (!in_array($schema_type, array('webpage', 'article', 'blogposting'), true)) {
            return array_merge($item_result, array('status' => 'manual_review_needed', 'skipped_reason' => 'schema_type_not_allowed'));
        }
        $is_consolidation = $this->aysa_agent8v3_schema_item_is_explicit_consolidation($item);
        if (!in_array(sanitize_key((string) ($item['conflict_status'] ?? 'none')), array('none', 'no_conflict', 'aysa_controlled'), true) && !$is_consolidation) {
            return array_merge($item_result, array('status' => 'manual_review_needed', 'skipped_reason' => 'provider_conflict'));
        }
        $decoded = json_decode($proposed_value, true);
        if (!is_array($decoded) || !$this->aysa_agent8v3_schema_payload_is_allowed($decoded, $schema_type, $url)) {
            return array_merge($item_result, array('status' => 'manual_review_needed', 'skipped_reason' => 'schema_validation_failed'));
        }
        $normalized_proposed = wp_json_encode($decoded, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        if ($is_consolidation) {
            $consolidation_check = $this->aysa_agent8v3_schema_consolidation_apply_precheck($item, $post_id, $url, $schema_type, (string) $normalized_proposed);
            if (empty($consolidation_check['ok'])) {
                return array_merge($item_result, array(
                    'status' => 'manual_review_needed',
                    'skipped_reason' => sanitize_key((string) ($consolidation_check['reason'] ?? 'schema_consolidation_precheck_failed')),
                    'schema_result_status' => sanitize_key((string) ($consolidation_check['reason'] ?? 'schema_consolidation_precheck_failed')),
                ));
            }
            $item['public_readback_backup'] = is_array($consolidation_check['public_readback'] ?? null) ? $consolidation_check['public_readback'] : array();
            $item['public_readback_hash'] = sanitize_text_field((string) ($consolidation_check['public_readback_hash'] ?? $item['public_readback_hash'] ?? ''));
            $item['proposed_jsonld_hash'] = sanitize_text_field((string) ($consolidation_check['proposed_jsonld_hash'] ?? $item['proposed_jsonld_hash'] ?? ''));
            $item['approval_token'] = sanitize_text_field((string) ($consolidation_check['approval_token'] ?? $item['approval_token'] ?? ''));
        }
        $current_row = $this->aysa_agent8v3_get_schema_row($url, $schema_type);
        $current_value = is_array($current_row) ? (string) ($current_row['schema_data'] ?? '') : '';
        $external_guard_readback = $this->aysa_agent8v3_schema_public_jsonld_readback($url, $schema_type, $current_value);
        $external_guard = $this->aysa_agent8v3_schema_external_apply_blocker($external_guard_readback);
        if ($external_guard !== '') {
            return array_merge($item_result, array(
                'status' => 'manual_review_needed',
                'skipped_reason' => $external_guard,
                'schema_result_status' => $external_guard,
                'external_schema_suppression_status' => 'external_schema_suppression_not_available',
                'public_readback_after' => $external_guard_readback,
            ));
        }
        $item_result['old_value'] = $current_value;
        $history_id = $this->aysa_agent8v3_create_schema_history_row($item, $context, $current_value);
        $item_result['history_id'] = $history_id;
        if (!empty($item['old_value_captured']) && $current_value !== $old_value) {
            $final = array_merge($item_result, array('status' => 'manual_review_needed', 'skipped_reason' => 'live_old_value_differs', 'actual_value' => $current_value));
            $this->aysa_agent8v3_update_schema_history_row($history_id, array('status' => 'skipped', 'actual_value' => $current_value, 'verification_status' => 'manual_review_needed', 'error_code' => 'live_old_value_differs'));
            return $final;
        }
        if ($current_value !== '' && $this->aysa_agent8v3_schema_json_equivalent($current_value, (string) $normalized_proposed)) {
            $final = array_merge($item_result, array('status' => 'skipped_no_change', 'verification_status' => 'verified_no_change', 'actual_value' => $current_value));
            $this->aysa_agent8v3_update_schema_history_row($history_id, array('status' => 'no_change', 'actual_value' => $current_value, 'verification_status' => 'verified_no_change', 'billing_status' => 'not_billable_no_change', 'billing_cost_cents' => 0));
            return $final;
        }

        $write_ok = $this->aysa_agent8v3_upsert_schema_row($url, $post_id, $schema_type, (string) $normalized_proposed);
        if (!$write_ok) {
            $final = array_merge($item_result, array('status' => 'failed', 'error_code' => 'schema_write_failed'));
            $this->aysa_agent8v3_update_schema_history_row($history_id, array('status' => 'failed', 'verification_status' => 'failed', 'error_code' => 'schema_write_failed'));
            return $final;
        }
        $readback = $this->aysa_agent8v3_get_schema_row($url, $schema_type);
        $actual = is_array($readback) ? (string) ($readback['schema_data'] ?? '') : '';
        if ($this->aysa_agent8v3_schema_json_equivalent($actual, (string) $normalized_proposed)) {
            $result_status = 'applied_cleanly';
            $public_after = array();
            if ($is_consolidation) {
                $public_after = $this->aysa_agent8v3_schema_public_jsonld_readback($url, $schema_type, $actual);
                if (sanitize_key((string) ($public_after['parse_status'] ?? '')) !== 'parsed') {
                    $final = array_merge($item_result, array(
                        'status' => 'verification_pending',
                        'changed' => true,
                        'verification_status' => 'verification_pending',
                        'actual_value' => $actual,
                        'schema_result_status' => 'verification_pending',
                    ));
                    $this->aysa_agent8v3_update_schema_history_row($history_id, array(
                        'status' => 'verification_pending',
                        'actual_value' => $actual,
                        'verification_status' => 'verification_pending',
                        'public_readback_after_json' => wp_json_encode($public_after, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
                    ));
                    return $final;
                }
                $matching_count = intval($public_after['matching_type_count'] ?? 0);
                $ownership = sanitize_key((string) ($public_after['matching_type_ownership'] ?? $public_after['ownership'] ?? 'unknown'));
                if ($matching_count > 1 || $ownership === 'mixed' || $ownership === 'non_aysa') {
                    $result_status = 'applied_but_external_duplicate_remains';
                }
            }
            $final = array_merge($item_result, array(
                'status' => 'verified',
                'changed' => true,
                'verification_status' => 'verified',
                'actual_value' => $actual,
                'schema_result_status' => $result_status,
                'public_readback_after' => $public_after,
            ));
            $this->aysa_agent8v3_update_schema_history_row($history_id, array(
                'status' => 'changed',
                'actual_value' => $actual,
                'verification_status' => 'verified',
                'schema_result_status' => $result_status,
                'public_readback_after_json' => $public_after === array() ? '' : wp_json_encode($public_after, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            ));
            return $final;
        }
        $final = array_merge($item_result, array('status' => 'failed', 'changed' => true, 'error_code' => 'verification_mismatch', 'verification_status' => 'failed', 'actual_value' => $actual));
        $this->aysa_agent8v3_update_schema_history_row($history_id, array('status' => 'failed', 'actual_value' => $actual, 'verification_status' => 'failed', 'error_code' => 'verification_mismatch'));
        return $final;
    }

    private function aysa_agent8v3_schema_payload_is_allowed($payload, $schema_type, $url) {
        if (!is_array($payload)) {
            return false;
        }
        $type = strtolower((string) ($payload['@type'] ?? ''));
        $allowed_type = strtolower((string) $schema_type);
        if ($allowed_type === 'blogposting') {
            $allowed_type = 'blogposting';
        }
        if ($type !== $allowed_type) {
            return false;
        }
        if (empty($payload['@context']) || empty($payload['url'])) {
            return false;
        }
        if (esc_url_raw((string) $payload['url']) === '') {
            return false;
        }
        if (in_array($type, array('article', 'blogposting'), true) && trim((string) ($payload['headline'] ?? '')) === '') {
            return false;
        }
        if ($type === 'webpage' && trim((string) ($payload['name'] ?? '')) === '') {
            return false;
        }
        return true;
    }

    private function aysa_agent8v3_schema_table_name() {
        global $wpdb;
        if (class_exists('AysaSchemaManager') && method_exists('AysaSchemaManager', 'maybe_create_schema_table')) {
            AysaSchemaManager::maybe_create_schema_table();
        }
        return $wpdb->prefix . 'aysa_schemas';
    }

    private function aysa_agent8v3_get_schema_row($url, $schema_type) {
        global $wpdb;
        $table = esc_sql($this->aysa_agent8v3_schema_table_name());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $row = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE url = %s AND schema_type = %s AND is_active = 1", esc_url_raw((string) $url), sanitize_key((string) $schema_type)), ARRAY_A);
        return is_array($row) ? $row : array();
    }

    private function aysa_agent8v3_upsert_schema_row($url, $post_id, $schema_type, $schema_data) {
        global $wpdb;
        $table = esc_sql($this->aysa_agent8v3_schema_table_name());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $existing = $wpdb->get_row($wpdb->prepare("SELECT id FROM $table WHERE url = %s AND schema_type = %s", esc_url_raw((string) $url), sanitize_key((string) $schema_type)), ARRAY_A);
        if (is_array($existing) && !empty($existing['id'])) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $updated = $wpdb->update($table, array(
                'page_id' => intval($post_id),
                'schema_data' => (string) $schema_data,
                'is_active' => 1,
                'updated_at' => current_time('mysql'),
            ), array('id' => intval($existing['id'])), array('%d', '%s', '%d', '%s'), array('%d'));
            return $updated !== false;
        }
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $inserted = $wpdb->insert($table, array(
            'url' => esc_url_raw((string) $url),
            'page_id' => intval($post_id),
            'schema_type' => sanitize_key((string) $schema_type),
            'schema_data' => (string) $schema_data,
            'is_active' => 1,
            'created_at' => current_time('mysql'),
            'updated_at' => current_time('mysql'),
        ), array('%s', '%d', '%s', '%s', '%d', '%s', '%s'));
        return $inserted !== false;
    }

    private function aysa_agent8v3_schema_json_equivalent($left, $right) {
        $a = json_decode((string) $left, true);
        $b = json_decode((string) $right, true);
        return is_array($a) && is_array($b) && wp_json_encode($a, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) === wp_json_encode($b, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    }

    private function aysa_agent8v3_schema_hash_value($value) {
        if (is_string($value)) {
            $decoded = json_decode($value, true);
            if (is_array($decoded)) {
                $value = $decoded;
            }
        }
        $encoded = wp_json_encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        if (!is_string($encoded) || $encoded === '') {
            $encoded = serialize($value);
        }
        return hash('sha256', (string) $encoded);
    }

    private function aysa_agent8v3_schema_public_readback_hash($readback) {
        $readback = is_array($readback) ? $readback : array();
        unset($readback['public_readback_timestamp']);
        return $this->aysa_agent8v3_schema_hash_value($readback);
    }

    private function aysa_agent8v3_schema_consolidation_approval_token($post_id, $url, $schema_type, $proposed_hash, $public_readback_hash) {
        return substr(hash('sha256', 'schema_consolidation|' . intval($post_id) . '|' . esc_url_raw((string) $url) . '|' . sanitize_key((string) $schema_type) . '|' . sanitize_text_field((string) $proposed_hash) . '|' . sanitize_text_field((string) $public_readback_hash)), 0, 32);
    }

    private function aysa_agent8v3_schema_item_is_explicit_consolidation($item) {
        $item = is_array($item) ? $item : array();
        return !empty($item['schema_consolidation_apply'])
            && sanitize_key((string) ($item['operation'] ?? '')) === 'technical_seo_schema_consolidation_apply'
            && sanitize_key((string) ($item['confirmation_kind'] ?? '')) === 'technical_schema_consolidation_apply';
    }

    private function aysa_agent8v3_schema_consolidation_apply_precheck($item, $post_id, $url, $schema_type, $normalized_proposed) {
        $item = is_array($item) ? $item : array();
        if (empty($item['backup_required']) || empty($item['rollback_required'])) {
            return array('ok' => false, 'reason' => 'schema_consolidation_backup_required');
        }
        $approval_token = sanitize_text_field((string) ($item['approval_token'] ?? ''));
        $expected_public_hash = sanitize_text_field((string) ($item['public_readback_hash'] ?? ''));
        $expected_proposed_hash = sanitize_text_field((string) ($item['proposed_jsonld_hash'] ?? ''));
        if ($approval_token === '' || $expected_public_hash === '' || $expected_proposed_hash === '') {
            return array('ok' => false, 'reason' => 'schema_consolidation_evidence_required');
        }
        $actual_proposed_hash = $this->aysa_agent8v3_schema_hash_value((string) $normalized_proposed);
        if (!hash_equals($expected_proposed_hash, $actual_proposed_hash)) {
            return array('ok' => false, 'reason' => 'proposed_jsonld_hash_mismatch');
        }
        $current_row = $this->aysa_agent8v3_get_schema_row($url, $schema_type);
        $current_schema = is_array($current_row) ? (string) ($current_row['schema_data'] ?? '') : '';
        $current_public_readback = $this->aysa_agent8v3_schema_public_jsonld_readback($url, $schema_type, $current_schema);
        $external_blocker = $this->aysa_agent8v3_schema_external_apply_blocker($current_public_readback);
        if ($external_blocker !== '') {
            return array(
                'ok' => false,
                'reason' => $external_blocker,
                'external_schema_suppression_status' => 'external_schema_suppression_not_available',
                'public_readback_hash' => $this->aysa_agent8v3_schema_public_readback_hash($current_public_readback),
                'public_readback' => $current_public_readback,
            );
        }
        $actual_public_hash = $this->aysa_agent8v3_schema_public_readback_hash($current_public_readback);
        if (!hash_equals($expected_public_hash, $actual_public_hash)) {
            return array(
                'ok' => false,
                'reason' => 'stale_public_readback',
                'public_readback_hash' => $actual_public_hash,
                'public_readback' => $current_public_readback,
            );
        }
        $expected_token = $this->aysa_agent8v3_schema_consolidation_approval_token($post_id, $url, $schema_type, $actual_proposed_hash, $actual_public_hash);
        if (!hash_equals($expected_token, $approval_token)) {
            return array('ok' => false, 'reason' => 'approval_token_mismatch');
        }
        return array(
            'ok' => true,
            'approval_token' => $expected_token,
            'public_readback_hash' => $actual_public_hash,
            'proposed_jsonld_hash' => $actual_proposed_hash,
            'public_readback' => $current_public_readback,
        );
    }

    private function aysa_agent8v3_schema_external_apply_blocker($public_readback) {
        $public_readback = is_array($public_readback) ? $public_readback : array();
        if (!empty($public_readback['same_type_external_schema_found'])) {
            return 'external_schema_source_requires_cleanup';
        }
        $sources = is_array($public_readback['matching_type_sources'] ?? null) ? array_map('sanitize_key', $public_readback['matching_type_sources']) : array();
        if (array_intersect($sources, array('unknown_external', 'unknown_plugin')) !== array()) {
            return 'external_schema_source_requires_cleanup';
        }
        $matching_count = intval($public_readback['matching_type_count'] ?? 0);
        $owner = sanitize_key((string) ($public_readback['matching_type_ownership'] ?? ''));
        if ($matching_count > 0 && in_array($owner, array('unknown', 'mixed', 'external', 'non_aysa'), true)) {
            return 'external_schema_source_requires_cleanup';
        }
        return '';
    }

    private function aysa_agent8v3_schema_history_rows() {
        $rows = get_option('aysa_agent8v3_schema_apply_history', array());
        return is_array($rows) ? array_values(array_filter($rows, 'is_array')) : array();
    }

    private function aysa_agent8v3_save_schema_history_rows($rows) {
        $rows = is_array($rows) ? array_values(array_filter($rows, 'is_array')) : array();
        update_option('aysa_agent8v3_schema_apply_history', array_slice($rows, -200), false);
    }

    private function aysa_agent8v3_create_schema_history_row($item, $context, $current_value) {
        $item = is_array($item) ? $item : array();
        $context = is_array($context) ? $context : array();
        $history_id = 'sah_' . time() . '_' . wp_generate_password(8, false, false);
        $row = array(
            'source' => 'agent8v3_schema_apply',
            'history_id' => $history_id,
            'project_id' => intval($context['project_id'] ?? 0),
            'site_host' => sanitize_text_field((string) ($context['site_host'] ?? $this->aysa_agent8v3_site_host())),
            'post_id' => intval($item['post_id'] ?? 0),
            'url' => esc_url_raw((string) ($item['url'] ?? '')),
            'schema_type' => sanitize_key((string) ($item['schema_type'] ?? '')),
            'old_value' => (string) $current_value,
            'new_value' => (string) ($item['proposed_schema_jsonld'] ?? $item['proposed_value'] ?? ''),
            'actual_value' => '',
            'source_run_id' => intval($context['source_run_id'] ?? 0),
            'payload_hash' => sanitize_text_field((string) ($context['payload_hash'] ?? '')),
            'idempotency_key' => sanitize_text_field((string) ($context['idempotency_key'] ?? '')),
            'timestamp' => current_time('mysql'),
            'updated_at' => current_time('mysql'),
            'billing_action_key' => 'agent8v3_schema_apply',
            'billing_status' => 'pending_result',
            'billing_cost_cents' => 0,
            'verification_status' => 'pending',
            'status' => 'submitted',
            'restore_available' => true,
            'restored_at' => '',
            'error_code' => '',
        );
        $rows = $this->aysa_agent8v3_schema_history_rows();
        $rows[] = $row;
        $this->aysa_agent8v3_save_schema_history_rows($rows);
        return $history_id;
    }

    private function aysa_agent8v3_update_schema_history_row($history_id, $updates) {
        $history_id = sanitize_text_field((string) $history_id);
        if ($history_id === '') {
            return;
        }
        $rows = $this->aysa_agent8v3_schema_history_rows();
        foreach ($rows as $index => $row) {
            if (sanitize_text_field((string) ($row['history_id'] ?? '')) !== $history_id) {
                continue;
            }
            foreach ((array) $updates as $key => $value) {
                $safe_key = sanitize_key((string) $key);
                if ($safe_key === '') {
                    continue;
                }
                if (in_array($safe_key, array('old_value', 'new_value', 'actual_value', 'restored_value', 'public_readback_backup_json', 'public_readback_after_json'), true)) {
                    $row[$safe_key] = (string) $value;
                } elseif ($safe_key === 'url') {
                    $row[$safe_key] = esc_url_raw((string) $value);
                } elseif (in_array($safe_key, array('billing_cost_cents', 'post_id', 'project_id', 'source_run_id'), true)) {
                    $row[$safe_key] = intval($value);
                } else {
                    $row[$safe_key] = sanitize_text_field((string) $value);
                }
            }
            $row['updated_at'] = current_time('mysql');
            $rows[$index] = $row;
            $this->aysa_agent8v3_save_schema_history_rows($rows);
            return;
        }
    }

    private function aysa_agent8v3_update_schema_history_billing($item_results, $billing_result) {
        $billing_result = is_array($billing_result) ? $billing_result : array();
        $unit_cost = intval($billing_result['unit_cost_cents'] ?? 5);
        $billing_status = sanitize_key((string) ($billing_result['status'] ?? 'not_billed'));
        foreach ((array) $item_results as $item_result) {
            if (!is_array($item_result) || empty($item_result['history_id'])) {
                continue;
            }
            $cost = (sanitize_key((string) ($item_result['status'] ?? '')) === 'verified' && !empty($item_result['changed']) && in_array($billing_status, array('billed', 'idempotent_replay'), true)) ? $unit_cost : 0;
            $this->aysa_agent8v3_update_schema_history_row($item_result['history_id'], array(
                'billing_status' => $cost > 0 ? $billing_status : 'not_billable',
                'billing_cost_cents' => $cost,
            ));
        }
    }

    private function aysa_agent8v3_bill_schema_apply_result($project_id, $payload, $result) {
        $idempotency_key = sanitize_text_field((string) ($payload['idempotency_key'] ?? ''));
        $verified_changed = 0;
        $post_ids = array();
        foreach ((array) ($result['item_results'] ?? array()) as $item) {
            if (is_array($item) && (string) ($item['status'] ?? '') === 'verified' && !empty($item['changed'])) {
                $verified_changed++;
                $post_ids[] = intval($item['post_id'] ?? 0);
            }
        }
        $unit_cost_cents = max(1, intval($payload['billing_unit_cents'] ?? 5));
        $cost_cents = $verified_changed * $unit_cost_cents;
        $base = array(
            'status' => $cost_cents > 0 ? 'pending' : 'not_billable_no_verified_changes',
            'unit_cost_cents' => $unit_cost_cents,
            'verified_changed_count' => $verified_changed,
            'cost_cents' => $cost_cents,
            'expected_cost_cents' => $cost_cents,
            'actual_cost_cents' => 0,
            'idempotency_key' => $idempotency_key,
            'action_key' => 'agent8v3_schema_apply',
        );
        if (intval($project_id) <= 0 || $idempotency_key === '' || $cost_cents <= 0) {
            return $base;
        }

        $records = get_option('aysa_agent8v3_schema_apply_billing_idempotency', array());
        $records = is_array($records) ? $records : array();
        $billing_key = hash('sha256', 'schema_apply_billing|' . intval($project_id) . '|' . $idempotency_key);
        if (isset($records[$billing_key]) && is_array($records[$billing_key])) {
            return array_merge($base, $records[$billing_key], array('status' => 'idempotent_replay'));
        }
        $wallet = $this->aysa_agent8v3_image_alt_test_wallet_manager();
        if (!$wallet || !method_exists($wallet, 'track_action_usage')) {
            return array_merge($base, array('status' => 'billing_failed', 'error' => 'wallet_unavailable'));
        }
        $reference = 'agent8v3_schema_apply:' . substr($idempotency_key, -24);
        if (method_exists($wallet, 'is_api_billed_action') && $wallet->is_api_billed_action('agent8v3_schema_apply')) {
            return array_merge($base, array(
                'status' => 'billing_pending_external_confirmation',
                'reference' => '',
                'api_ledger_reference' => '',
                'billing_authority' => 'api_wallet',
                'reason' => 'api_wallet_ledger_reference_required',
            ));
        }
        $ok = $wallet->track_action_usage(
            intval($project_id),
            'agent8v3_schema_apply',
            $reference,
            array(
                'billing_policy' => 'verified_changed_schema_blocks_only_cost_x5',
                'billing_context' => 'normal_agent8_flow',
                'billing_idempotency_key' => $billing_key,
                'payload_hash' => sanitize_text_field((string) ($payload['payload_hash'] ?? '')),
                'verified_changed_count' => $verified_changed,
                'unit_cost_cents' => $unit_cost_cents,
                'override_cost_cents' => $cost_cents,
                'post_ids' => array_values(array_filter(array_map('intval', $post_ids))),
            ),
            $cost_cents,
            'action:agent8v3_schema_apply'
        );
        if (!$ok) {
            return array_merge($base, array('status' => 'billing_failed', 'error' => 'wallet_debit_failed'));
        }
        $record = array(
            'status' => 'billed',
            'cost_cents' => $cost_cents,
            'actual_cost_cents' => $cost_cents,
            'unit_cost_cents' => $unit_cost_cents,
            'verified_changed_count' => $verified_changed,
            'reference' => $reference,
            'api_ledger_reference' => $reference,
            'action_key' => 'agent8v3_schema_apply',
            'billed_at' => current_time('mysql'),
        );
        $records[$billing_key] = $record;
        update_option('aysa_agent8v3_schema_apply_billing_idempotency', array_slice($records, -100, null, true), false);
        return array_merge($base, $record);
    }

    private function aysa_agent8v3_schema_api_wallet_requires_external_ledger() {
        $wallet = $this->aysa_agent8v3_image_alt_test_wallet_manager();
        return $wallet
            && method_exists($wallet, 'is_api_billed_action')
            && $wallet->is_api_billed_action('agent8v3_schema_apply');
    }

    private function aysa_agent8v3_technical_schema_restore_bridge($project_id, $payload) {
        $history_id = sanitize_text_field((string) ($payload['history_id'] ?? ''));
        $result = array(
            'operation' => 'technical_schema_restore_bridge',
            'module' => 'schema',
            'project_id' => intval($project_id),
            'history_id' => $history_id,
            'billing_status' => 'not_billable_restore',
            'billing_cost_cents' => 0,
            'actual_credits_cents' => 0,
            'writes_enabled' => false,
        );
        if ($history_id === '') {
            return array_merge($result, array('status' => 'failed', 'reason' => 'history_id_required'));
        }
        foreach ($this->aysa_agent8v3_schema_history_rows() as $row) {
            if (sanitize_text_field((string) ($row['history_id'] ?? '')) !== $history_id) {
                continue;
            }
            $url = esc_url_raw((string) ($row['url'] ?? ''));
            $post_id = intval($row['post_id'] ?? 0);
            $schema_type = sanitize_key((string) ($row['schema_type'] ?? ''));
            $old_value = (string) ($row['old_value'] ?? '');
            if (sanitize_key((string) ($row['restore_status'] ?? '')) === 'verified' || (string) ($row['restored_at'] ?? '') !== '') {
                return array_merge($result, array(
                    'status' => 'already_rolled_back',
                    'writes_enabled' => false,
                    'post_id' => $post_id,
                    'schema_type' => $schema_type,
                    'completed_at' => sanitize_text_field((string) ($row['restored_at'] ?? '')),
                ));
            }
            if ($url === '' || $schema_type === '') {
                return array_merge($result, array('status' => 'failed', 'reason' => 'restore_target_missing'));
            }
            if ($old_value === '') {
                $this->aysa_agent8v3_deactivate_schema_row($url, $schema_type);
                $actual_row = $this->aysa_agent8v3_get_schema_row($url, $schema_type);
                $verified = $actual_row === array();
                $actual = '';
            } else {
                $this->aysa_agent8v3_upsert_schema_row($url, $post_id, $schema_type, $old_value);
                $actual_row = $this->aysa_agent8v3_get_schema_row($url, $schema_type);
                $actual = is_array($actual_row) ? (string) ($actual_row['schema_data'] ?? '') : '';
                $verified = $this->aysa_agent8v3_schema_json_equivalent($actual, $old_value);
            }
            $this->aysa_agent8v3_update_schema_history_row($history_id, array(
                'restored_at' => current_time('mysql'),
                'restored_value' => $actual,
                'restore_status' => $verified ? 'verified' : 'failed',
            ));
            return array_merge($result, array(
                'status' => $verified ? 'verified' : 'failed',
                'writes_enabled' => true,
                'post_id' => $post_id,
                'schema_type' => $schema_type,
                'actual_value' => $actual,
                'completed_at' => current_time('mysql'),
            ));
        }
        return array_merge($result, array('status' => 'failed', 'reason' => 'history_not_found'));
    }

    private function aysa_agent8v3_deactivate_schema_row($url, $schema_type) {
        global $wpdb;
        $table = esc_sql($this->aysa_agent8v3_schema_table_name());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $wpdb->update($table, array(
            'is_active' => 0,
            'updated_at' => current_time('mysql'),
        ), array(
            'url' => esc_url_raw((string) $url),
            'schema_type' => sanitize_key((string) $schema_type),
        ), array('%d', '%s'), array('%s', '%s'));
    }

    private function aysa_agent8v3_metadata_product_gates() {
        $project_gate = get_option('aysa_agent8v3_metadata_project_gate_enabled', '0') === '1';
        $site_gate = get_option('aysa_agent8v3_metadata_apply_enabled', '0') === '1'
            || get_option('aysa_agent8v3_metadata_site_gate_enabled', '0') === '1';
        $kill_switch = $this->aysa_agent8v3_env_flag('AYSA_AGENT8V3_TECHNICAL_APPLY_KILL_SWITCH')
            || $this->aysa_agent8v3_env_flag('AYSA_AGENT8V3_METADATA_APPLY_KILL_SWITCH');
        return array(
            'module' => 'metadata',
            'project_gate_enabled' => $project_gate,
            'api_project_gate_enabled' => $project_gate,
            'site_gate_enabled' => $site_gate,
            'plugin_site_gate_enabled' => $site_gate,
            'global_kill_switch_active' => $kill_switch,
            'normal_flow_enabled' => $project_gate && $site_gate && !$kill_switch,
            'default_state' => 'off',
            'source' => 'plugin_admin_options',
        );
    }

    public function handle_agent8v3_metadata_product_gates_set() {
        if (!$this->aysa_agent8v3_image_alt_test_authorized()) {
            wp_send_json_error(array('message' => 'Unauthorized'), 403);
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_gate = !empty($_POST['project_gate_enabled']) && sanitize_text_field((string) wp_unslash($_POST['project_gate_enabled'])) !== '0';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $site_gate = !empty($_POST['site_gate_enabled']) && sanitize_text_field((string) wp_unslash($_POST['site_gate_enabled'])) !== '0';

        update_option('aysa_agent8v3_metadata_project_gate_enabled', $project_gate ? '1' : '0', false);
        update_option('aysa_agent8v3_metadata_site_gate_enabled', $site_gate ? '1' : '0', false);
        update_option('aysa_agent8v3_metadata_apply_enabled', $site_gate ? '1' : '0', false);

        wp_send_json_success(array(
            'product_gates' => $this->aysa_agent8v3_metadata_product_gates(),
            'billing_status' => 'metadata_apply_real_billing',
            'billing_unit_cents' => 5,
        ));
    }

    private function aysa_agent8v3_technical_metadata_apply_bridge($project_id, $payload) {
        $selected_items = is_array($payload['selected_items'] ?? null) ? array_values($payload['selected_items']) : array();
        $idempotency_key = sanitize_text_field((string) ($payload['idempotency_key'] ?? ''));
        $payload_hash = sanitize_text_field((string) ($payload['payload_hash'] ?? ''));
        $result = array(
            'operation' => 'technical_metadata_apply_bridge',
            'module' => 'metadata',
            'project_id' => intval($project_id),
            'payload_hash' => $payload_hash,
            'idempotency_key' => $idempotency_key,
            'accepted_count' => count($selected_items),
            'applied_count' => 0,
            'verified_count' => 0,
            'applied_pending_verification_count' => 0,
            'failed_count' => 0,
            'skipped_count' => 0,
            'manual_needed_count' => 0,
            'item_results' => array(),
            'dry_run' => !empty($payload['dry_run']),
            'writes_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'billing_action_key' => 'agent8v3_metadata_apply',
            'billing_cost_cents' => 0,
            'actual_credits_cents' => 0,
        );

        $billing_status = sanitize_key((string) ($payload['billing_status'] ?? ''));
        $payload_product_gates = isset($payload['product_gates']) && is_array($payload['product_gates']) ? $payload['product_gates'] : array();
        $local_product_gates = $this->aysa_agent8v3_metadata_product_gates();
        $project_gate_enabled = !empty($payload_product_gates['project_gate_enabled']) && !empty($local_product_gates['project_gate_enabled']);
        $site_gate_enabled = !empty($payload_product_gates['site_gate_enabled']) && !empty($local_product_gates['site_gate_enabled']);
        $kill_switch_active = !empty($local_product_gates['global_kill_switch_active']) || !empty($payload_product_gates['global_kill_switch_active']);
        $normal_agent8_flow_allowed = $billing_status === 'metadata_apply_real_billing'
            && $project_gate_enabled
            && $site_gate_enabled
            && !$kill_switch_active;
        $result['billing_status'] = $billing_status === 'metadata_apply_real_billing' ? 'metadata_apply_real_billing' : 'metadata_apply_real_billing_blocked';
        $result['billing_policy'] = 'verified_changed_metadata_fields_only_cost_x5';
        $result['product_gates'] = array(
            'payload' => $payload_product_gates,
            'local' => $local_product_gates,
            'project_gate_enabled' => $project_gate_enabled,
            'site_gate_enabled' => $site_gate_enabled,
            'global_kill_switch_active' => $kill_switch_active,
        );
        $gate_reasons = array();
        if (sanitize_key((string) ($payload['module'] ?? '')) !== 'metadata') {
            $gate_reasons[] = 'module_not_metadata';
        }
        if (empty($payload['user_confirmed'])) {
            $gate_reasons[] = 'user_confirmation_required';
        }
        if ($selected_items === array()) {
            $gate_reasons[] = 'selected_metadata_rows_required';
        }
        if ($idempotency_key === '') {
            $gate_reasons[] = 'idempotency_key_required';
        }
        if (!$project_gate_enabled) {
            $gate_reasons[] = 'metadata_project_gate_disabled';
        }
        if (!$site_gate_enabled) {
            $gate_reasons[] = 'metadata_site_gate_disabled';
        }
        if ($kill_switch_active) {
            $gate_reasons[] = 'metadata_global_kill_switch_active';
        }
        if ($billing_status !== 'metadata_apply_real_billing') {
            $gate_reasons[] = 'metadata_real_billing_required';
        }
        if (sanitize_key((string) ($payload['billing_action_key'] ?? '')) !== 'agent8v3_metadata_apply') {
            $gate_reasons[] = 'metadata_billing_action_required';
        }
        if (!empty($payload['dry_run']) || empty($payload['writes_enabled'])) {
            $gate_reasons[] = 'writes_not_requested';
        }
        if (!$normal_agent8_flow_allowed && $gate_reasons === array()) {
            $gate_reasons[] = 'metadata_agent8_flow_disabled';
        }
        if ($gate_reasons !== array()) {
            $result['status'] = $selected_items === array() ? 'no_executable_fixes' : 'blocked';
            $result['reason'] = $gate_reasons[0];
            $result['gate_reasons'] = array_values(array_unique($gate_reasons));
            return $result;
        }

        $unit_cost_cents = max(1, intval($payload['billing_unit_cents'] ?? 5));
        $max_cost_cents = max(count($selected_items) * $unit_cost_cents, intval($payload['max_cost_cents'] ?? 0));
        $billing_preflight = $this->aysa_agent8v3_image_alt_test_billing_preflight($max_cost_cents, $idempotency_key, 'metadata_agent8_flow');
        $result['billing_preflight'] = $billing_preflight;
        if (empty($billing_preflight['ok'])) {
            $result['status'] = 'blocked';
            $result['reason'] = 'metadata_billing_preflight_failed';
            $result['gate_reasons'] = array('metadata_billing_preflight_failed');
            $result['billing_status'] = 'billing_preflight_failed';
            return $result;
        }

        $items_hash = hash('sha256', wp_json_encode($selected_items));
        $records = get_option('aysa_agent8v3_metadata_apply_idempotency', array());
        $records = is_array($records) ? $records : array();
        if (isset($records[$idempotency_key]) && is_array($records[$idempotency_key])) {
            $record = $records[$idempotency_key];
            if (sanitize_text_field((string) ($record['items_hash'] ?? '')) !== $items_hash) {
                $result['status'] = 'idempotency_conflict';
                $result['reason'] = 'idempotency_key_payload_mismatch';
                return $result;
            }
            $replay = is_array($record['result'] ?? null) ? $record['result'] : array();
            $replay['idempotent_replay'] = true;
            return array_merge($result, $replay);
        }

        $apply_context = array(
            'project_id' => intval($project_id),
            'site_host' => $this->aysa_agent8v3_site_host(),
            'idempotency_key' => $idempotency_key,
            'payload_hash' => $payload_hash,
            'source_run_id' => intval($payload['parent_technical_run_id'] ?? $payload['run_id'] ?? 0),
        );
        foreach ($selected_items as $item) {
            $item_result = $this->aysa_agent8v3_apply_single_metadata_item($item, $apply_context);
            $result['item_results'][] = $item_result;
            $status = sanitize_key((string) ($item_result['status'] ?? 'failed'));
            if ($status === 'verified') {
                $result['verified_count']++;
                $result['applied_count']++;
            } elseif (str_starts_with($status, 'skipped')) {
                $result['skipped_count']++;
            } elseif ($status === 'manual_review_needed') {
                $result['manual_needed_count']++;
            } else {
                $result['failed_count']++;
            }
        }

        if ($result['verified_count'] > 0 && $result['failed_count'] === 0 && $result['manual_needed_count'] === 0) {
            $result['status'] = 'verified';
        } elseif ($result['verified_count'] > 0) {
            $result['status'] = 'partial_failed';
        } elseif ($result['skipped_count'] > 0 && $result['failed_count'] === 0 && $result['manual_needed_count'] === 0) {
            $result['status'] = 'skipped';
        } elseif ($result['manual_needed_count'] > 0 && $result['failed_count'] === 0) {
            $result['status'] = 'manual_review_needed';
        } else {
            $result['status'] = 'failed';
        }
        $result['writes_enabled'] = true;
        $result['completed_at'] = current_time('mysql');
        $billing_result = $this->aysa_agent8v3_bill_metadata_apply_result($project_id, $payload, $result);
        $this->aysa_agent8v3_update_metadata_history_billing(is_array($result['item_results'] ?? null) ? $result['item_results'] : array(), $billing_result);
        $result['billing'] = $billing_result;
        $result['billing_status'] = sanitize_key((string) ($billing_result['status'] ?? 'billing_not_applicable'));
        $result['billing_cost_cents'] = intval($billing_result['cost_cents'] ?? 0);
        $result['actual_credits_cents'] = intval($billing_result['cost_cents'] ?? 0);
        $result['billing_external_recorded'] = in_array($result['billing_status'], array('billed', 'idempotent_replay', 'not_billable_no_verified_changes'), true);
        $result['billing_reference'] = sanitize_text_field((string) ($billing_result['reference'] ?? ''));

        $records[$idempotency_key] = array(
            'items_hash' => $items_hash,
            'result' => $result,
            'timestamp' => current_time('mysql'),
        );
        update_option('aysa_agent8v3_metadata_apply_idempotency', array_slice($records, -100, null, true), false);
        return $result;
    }

    private function aysa_agent8v3_apply_single_metadata_item($item, $context = array()) {
        $item = is_array($item) ? $item : array();
        $context = is_array($context) ? $context : array();
        $post_id = intval($item['post_id'] ?? 0);
        $field = sanitize_key((string) ($item['field'] ?? ''));
        $provider = sanitize_key((string) ($item['provider'] ?? 'unknown'));
        $old_value = sanitize_text_field((string) ($item['old_value'] ?? ''));
        $proposed_value = sanitize_text_field((string) ($item['proposed_value'] ?? ''));
        $item_result = array(
            'item_id' => sanitize_text_field((string) ($item['item_id'] ?? $item['row_id'] ?? $post_id . '_' . $field)),
            'post_id' => $post_id,
            'url' => esc_url_raw((string) ($item['url'] ?? '')),
            'field' => $field,
            'provider' => $provider,
            'old_value' => $old_value,
            'proposed_value' => $proposed_value,
            'actual_value' => '',
            'changed' => false,
            'verification_status' => 'not_verified',
        );
        if (empty($item['accepted_by_user'])) {
            return array_merge($item_result, array('status' => 'skipped_not_accepted', 'skipped_reason' => 'item_not_accepted'));
        }
        if ($post_id <= 0 || !get_post($post_id)) {
            return array_merge($item_result, array('status' => 'skipped_missing_item', 'skipped_reason' => 'post_missing'));
        }
        if (!in_array($field, array('title', 'meta_description'), true)) {
            return array_merge($item_result, array('status' => 'manual_review_needed', 'skipped_reason' => 'metadata_field_not_allowed'));
        }
        if ($proposed_value === '') {
            return array_merge($item_result, array('status' => 'failed', 'error_code' => 'proposed_value_empty'));
        }
        $key_info = $this->aysa_agent8v3_metadata_write_keys($field, $provider);
        if (empty($key_info['ok'])) {
            return array_merge($item_result, array('status' => 'manual_review_needed', 'skipped_reason' => sanitize_key((string) ($key_info['reason'] ?? 'provider_conflict'))));
        }
        $meta_key = sanitize_key((string) ($key_info['primary_key'] ?? ''));
        if ($meta_key === '') {
            return array_merge($item_result, array('status' => 'manual_review_needed', 'skipped_reason' => 'metadata_key_missing'));
        }
        $current_value = sanitize_text_field((string) get_post_meta($post_id, $meta_key, true));
        $item_result['old_value'] = $current_value;
        $history_id = $this->aysa_agent8v3_create_metadata_history_row($item, $context, $meta_key, (string) ($key_info['provider'] ?? $provider), $current_value);
        $item_result['history_id'] = $history_id;
        if (array_key_exists('old_value', $item) && !empty($item['old_value_captured']) && $current_value !== $old_value) {
            $final = array_merge($item_result, array('status' => 'manual_review_needed', 'skipped_reason' => 'live_old_value_differs', 'actual_value' => $current_value));
            $this->aysa_agent8v3_update_metadata_history_row($history_id, array(
                'status' => 'skipped',
                'actual_value' => $current_value,
                'verification_status' => 'manual_review_needed',
                'error_code' => 'live_old_value_differs',
            ));
            return $final;
        }
        if ($current_value === $proposed_value) {
            $final = array_merge($item_result, array('status' => 'skipped_no_change', 'verification_status' => 'verified_no_change', 'actual_value' => $current_value));
            $this->aysa_agent8v3_update_metadata_history_row($history_id, array(
                'status' => 'no_change',
                'actual_value' => $current_value,
                'verification_status' => 'verified_no_change',
                'billing_status' => 'not_billable_no_change',
                'billing_cost_cents' => 0,
            ));
            return $final;
        }

        update_post_meta($post_id, $meta_key, $proposed_value);
        foreach ((array) ($key_info['mirror_keys'] ?? array()) as $mirror_key) {
            $mirror_key = sanitize_key((string) $mirror_key);
            if ($mirror_key !== '' && $mirror_key !== $meta_key) {
                update_post_meta($post_id, $mirror_key, $proposed_value);
            }
        }
        update_post_meta($post_id, '_aysa_agent8v3_metadata_apply_source', 'agent8v3_technical_apply');
        update_post_meta($post_id, '_aysa_agent8v3_metadata_apply_hash', hash('sha256', $post_id . '|' . $field . '|' . $proposed_value));
        update_post_meta($post_id, '_aysa_agent8v3_metadata_apply_updated_at', current_time('mysql'));

        $verified_value = sanitize_text_field((string) get_post_meta($post_id, $meta_key, true));
        if ($verified_value === $proposed_value) {
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reviewed WordPress query: the metadata selector is required by the feature and is constrained by the surrounding post type, status, limit, or administrative scope.
            $final = array_merge($item_result, array('status' => 'verified', 'changed' => true, 'verification_status' => 'verified', 'actual_value' => $verified_value, 'meta_key' => $meta_key, 'provider' => (string) ($key_info['provider'] ?? $provider)));
            $this->aysa_agent8v3_update_metadata_history_row($history_id, array(
                'status' => 'changed',
                'actual_value' => $verified_value,
                'verification_status' => 'verified',
                // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reviewed WordPress query: the metadata selector is required by the feature and is constrained by the surrounding post type, status, limit, or administrative scope.
                'meta_key' => $meta_key,
                'provider' => (string) ($key_info['provider'] ?? $provider),
            ));
            return $final;
        }
        $final = array_merge($item_result, array('status' => 'failed', 'changed' => true, 'error_code' => 'verification_mismatch', 'verification_status' => 'failed', 'actual_value' => $verified_value));
        $this->aysa_agent8v3_update_metadata_history_row($history_id, array(
            'status' => 'failed',
            'actual_value' => $verified_value,
            'verification_status' => 'failed',
            'error_code' => 'verification_mismatch',
        ));
        return $final;
    }

    private function aysa_agent8v3_metadata_write_keys($field, $provider) {
        $field = sanitize_key((string) $field);
        $provider = sanitize_key((string) $provider);
        if (!in_array($field, array('title', 'meta_description'), true)) {
            return array('ok' => false, 'reason' => 'metadata_field_not_allowed');
        }
        $active = $this->aysa_agent8v3_active_metadata_provider();
        if ($provider === '' || $provider === 'unknown') {
            $provider = $active !== '' ? $active : 'aysa';
        }
        if (in_array($provider, array('yoast', 'rank_math'), true) && $active !== $provider) {
            return array('ok' => false, 'reason' => 'provider_conflict');
        }
        if ($provider === 'yoast') {
            return array(
                'ok' => true,
                'provider' => 'yoast',
                'primary_key' => $field === 'title' ? '_yoast_wpseo_title' : '_yoast_wpseo_metadesc',
                'mirror_keys' => $field === 'title' ? array('_aysa_seo_title', '_aysa_meta_title') : array('_aysa_seo_description', '_aysa_meta_description'),
            );
        }
        if ($provider === 'rank_math') {
            return array(
                'ok' => true,
                'provider' => 'rank_math',
                'primary_key' => $field === 'title' ? 'rank_math_title' : 'rank_math_description',
                'mirror_keys' => $field === 'title' ? array('_aysa_seo_title', '_aysa_meta_title') : array('_aysa_seo_description', '_aysa_meta_description'),
            );
        }
        if ($active !== '' && $provider === 'aysa') {
            return array('ok' => false, 'reason' => 'provider_conflict');
        }
        return array(
            'ok' => true,
            'provider' => 'aysa',
            'primary_key' => $field === 'title' ? '_aysa_seo_title' : '_aysa_seo_description',
            'mirror_keys' => $field === 'title' ? array('_aysa_meta_title') : array('_aysa_meta_description'),
        );
    }

    private function aysa_agent8v3_active_metadata_provider() {
        if (defined('WPSEO_VERSION') || class_exists('WPSEO_Options') || defined('WPSEO_FILE')) {
            return 'yoast';
        }
        if (defined('RANK_MATH_VERSION') || class_exists('RankMath')) {
            return 'rank_math';
        }
        if (function_exists('is_plugin_active')) {
            if (is_plugin_active('wordpress-seo/wp-seo.php') || is_plugin_active('wordpress-seo-premium/wp-seo-premium.php')) {
                return 'yoast';
            }
            if (is_plugin_active('seo-by-rank-math/rank-math.php')) {
                return 'rank_math';
            }
        }
        return '';
    }

    private function aysa_agent8v3_metadata_history_rows() {
        $rows = get_option('aysa_agent8v3_metadata_apply_history', array());
        return is_array($rows) ? array_values(array_filter($rows, 'is_array')) : array();
    }

    private function aysa_agent8v3_save_metadata_history_rows($rows) {
        $rows = is_array($rows) ? array_values(array_filter($rows, 'is_array')) : array();
        update_option('aysa_agent8v3_metadata_apply_history', array_slice($rows, -200), false);
    }

    private function aysa_agent8v3_create_metadata_history_row($item, $context, $meta_key, $provider, $current_value) {
        $item = is_array($item) ? $item : array();
        $context = is_array($context) ? $context : array();
        $history_id = 'mah_' . time() . '_' . wp_generate_password(8, false, false);
        $row = array(
            'source' => 'agent8v3_metadata_apply',
            'history_id' => $history_id,
            'project_id' => intval($context['project_id'] ?? 0),
            'site_host' => sanitize_text_field((string) ($context['site_host'] ?? $this->aysa_agent8v3_site_host())),
            'post_id' => intval($item['post_id'] ?? 0),
            'url' => esc_url_raw((string) ($item['url'] ?? '')),
            'field' => sanitize_key((string) ($item['field'] ?? '')),
            'provider' => sanitize_key((string) $provider),
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reviewed WordPress query: the metadata selector is required by the feature and is constrained by the surrounding post type, status, limit, or administrative scope.
            'meta_key' => sanitize_key((string) $meta_key),
            'old_value' => sanitize_text_field((string) $current_value),
            'new_value' => sanitize_text_field((string) ($item['proposed_value'] ?? '')),
            'actual_value' => '',
            'source_run_id' => intval($context['source_run_id'] ?? 0),
            'payload_hash' => sanitize_text_field((string) ($context['payload_hash'] ?? '')),
            'idempotency_key' => sanitize_text_field((string) ($context['idempotency_key'] ?? '')),
            'timestamp' => current_time('mysql'),
            'updated_at' => current_time('mysql'),
            'billing_action_key' => 'agent8v3_metadata_apply',
            'billing_status' => 'pending_result',
            'billing_cost_cents' => 0,
            'verification_status' => 'pending',
            'status' => 'submitted',
            'restore_available' => true,
            'restored_at' => '',
            'error_code' => '',
        );
        $rows = $this->aysa_agent8v3_metadata_history_rows();
        $rows[] = $row;
        $this->aysa_agent8v3_save_metadata_history_rows($rows);
        return $history_id;
    }

    private function aysa_agent8v3_update_metadata_history_row($history_id, $updates) {
        $history_id = sanitize_text_field((string) $history_id);
        if ($history_id === '') {
            return;
        }
        $rows = $this->aysa_agent8v3_metadata_history_rows();
        foreach ($rows as $index => $row) {
            if (sanitize_text_field((string) ($row['history_id'] ?? '')) !== $history_id) {
                continue;
            }
            foreach ((array) $updates as $key => $value) {
                $safe_key = sanitize_key((string) $key);
                if ($safe_key === '') {
                    continue;
                }
                if (in_array($safe_key, array('old_value', 'new_value', 'actual_value', 'restored_value'), true)) {
                    $row[$safe_key] = sanitize_text_field((string) $value);
                } elseif (in_array($safe_key, array('url'), true)) {
                    $row[$safe_key] = esc_url_raw((string) $value);
                } elseif (in_array($safe_key, array('billing_cost_cents', 'post_id', 'project_id', 'source_run_id'), true)) {
                    $row[$safe_key] = intval($value);
                } else {
                    $row[$safe_key] = sanitize_text_field((string) $value);
                }
            }
            $row['updated_at'] = current_time('mysql');
            $rows[$index] = $row;
            $this->aysa_agent8v3_save_metadata_history_rows($rows);
            return;
        }
    }

    private function aysa_agent8v3_update_metadata_history_billing($item_results, $billing_result) {
        $billing_result = is_array($billing_result) ? $billing_result : array();
        $unit_cost = intval($billing_result['unit_cost_cents'] ?? 5);
        $billing_status = sanitize_key((string) ($billing_result['status'] ?? 'not_billed'));
        foreach ((array) $item_results as $item_result) {
            if (!is_array($item_result) || empty($item_result['history_id'])) {
                continue;
            }
            $cost = (sanitize_key((string) ($item_result['status'] ?? '')) === 'verified' && !empty($item_result['changed']) && in_array($billing_status, array('billed', 'idempotent_replay'), true)) ? $unit_cost : 0;
            $this->aysa_agent8v3_update_metadata_history_row($item_result['history_id'], array(
                'billing_status' => $cost > 0 ? $billing_status : 'not_billable',
                'billing_cost_cents' => $cost,
            ));
        }
    }

    private function aysa_agent8v3_bill_metadata_apply_result($project_id, $payload, $result) {
        $idempotency_key = sanitize_text_field((string) ($payload['idempotency_key'] ?? ''));
        $verified_changed = 0;
        $post_ids = array();
        foreach ((array) ($result['item_results'] ?? array()) as $item) {
            if (!is_array($item)) {
                continue;
            }
            if ((string) ($item['status'] ?? '') === 'verified' && !empty($item['changed'])) {
                $verified_changed++;
                $post_ids[] = intval($item['post_id'] ?? 0);
            }
        }
        $unit_cost_cents = max(1, intval($payload['billing_unit_cents'] ?? 5));
        $cost_cents = $verified_changed * $unit_cost_cents;
        $base = array(
            'status' => $cost_cents > 0 ? 'pending' : 'not_billable_no_verified_changes',
            'unit_cost_cents' => $unit_cost_cents,
            'verified_changed_count' => $verified_changed,
            'cost_cents' => $cost_cents,
            'idempotency_key' => $idempotency_key,
        );
        if (intval($project_id) <= 0 || $idempotency_key === '' || $cost_cents <= 0) {
            return $base;
        }

        $records = get_option('aysa_agent8v3_metadata_apply_billing_idempotency', array());
        $records = is_array($records) ? $records : array();
        $billing_key = hash('sha256', 'metadata_apply_billing|' . intval($project_id) . '|' . $idempotency_key);
        if (isset($records[$billing_key]) && is_array($records[$billing_key])) {
            return array_merge($base, $records[$billing_key], array('status' => 'idempotent_replay'));
        }

        $wallet = $this->aysa_agent8v3_image_alt_test_wallet_manager();
        if (!$wallet || !method_exists($wallet, 'track_action_usage')) {
            return array_merge($base, array('status' => 'billing_failed', 'error' => 'wallet_unavailable'));
        }

        $reference = 'agent8v3_metadata_apply:' . substr($idempotency_key, -24);
        $meta = array(
            'billing_policy' => 'verified_changed_metadata_fields_only_cost_x5',
            'billing_context' => 'normal_agent8_flow',
            'billing_idempotency_key' => $billing_key,
            'payload_hash' => sanitize_text_field((string) ($payload['payload_hash'] ?? '')),
            'verified_changed_count' => $verified_changed,
            'unit_cost_cents' => $unit_cost_cents,
            'override_cost_cents' => $cost_cents,
            'post_ids' => array_values(array_filter(array_map('intval', $post_ids))),
        );
        $ok = $wallet->track_action_usage(
            intval($project_id),
            'agent8v3_metadata_apply',
            $reference,
            $meta,
            $cost_cents,
            'action:agent8v3_metadata_apply'
        );
        if (!$ok) {
            return array_merge($base, array('status' => 'billing_failed', 'error' => 'wallet_debit_failed'));
        }

        $record = array(
            'status' => 'billed',
            'cost_cents' => $cost_cents,
            'unit_cost_cents' => $unit_cost_cents,
            'verified_changed_count' => $verified_changed,
            'reference' => $reference,
            'action_key' => 'agent8v3_metadata_apply',
            'billed_at' => current_time('mysql'),
        );
        $records[$billing_key] = $record;
        update_option('aysa_agent8v3_metadata_apply_billing_idempotency', array_slice($records, -100, null, true), false);
        return array_merge($base, $record);
    }

    private function aysa_agent8v3_technical_metadata_restore_bridge($project_id, $payload) {
        $history_id = sanitize_text_field((string) ($payload['history_id'] ?? ''));
        $result = array(
            'operation' => 'technical_metadata_restore_bridge',
            'module' => 'metadata',
            'project_id' => intval($project_id),
            'history_id' => $history_id,
            'billing_status' => 'not_billable_restore',
            'billing_cost_cents' => 0,
            'actual_credits_cents' => 0,
            'writes_enabled' => false,
        );
        if ($history_id === '') {
            return array_merge($result, array('status' => 'failed', 'reason' => 'history_id_required'));
        }
        $rows = $this->aysa_agent8v3_metadata_history_rows();
        foreach ($rows as $row) {
            if (sanitize_text_field((string) ($row['history_id'] ?? '')) !== $history_id) {
                continue;
            }
            $post_id = intval($row['post_id'] ?? 0);
            $meta_key = sanitize_key((string) ($row['meta_key'] ?? ''));
            $old_value = sanitize_text_field((string) ($row['old_value'] ?? ''));
            if ($post_id <= 0 || $meta_key === '' || !get_post($post_id)) {
                return array_merge($result, array('status' => 'failed', 'reason' => 'restore_target_missing'));
            }
            update_post_meta($post_id, $meta_key, $old_value);
            $actual = sanitize_text_field((string) get_post_meta($post_id, $meta_key, true));
            $verified = $actual === $old_value;
            $this->aysa_agent8v3_update_metadata_history_row($history_id, array(
                'restored_at' => current_time('mysql'),
                'restored_value' => $actual,
                'restore_status' => $verified ? 'verified' : 'failed',
            ));
            return array_merge($result, array(
                'status' => $verified ? 'verified' : 'failed',
                'writes_enabled' => true,
                'post_id' => $post_id,
                // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reviewed WordPress query: the metadata selector is required by the feature and is constrained by the surrounding post type, status, limit, or administrative scope.
                'meta_key' => $meta_key,
                'actual_value' => $actual,
                'completed_at' => current_time('mysql'),
            ));
        }
        return array_merge($result, array('status' => 'failed', 'reason' => 'history_not_found'));
    }

    private function aysa_agent8v3_image_optimization_product_gates() {
        $project_gate = get_option('aysa_agent8v3_image_optimization_project_gate_enabled', '0') === '1';
        $site_gate = get_option('aysa_agent8v3_image_optimization_apply_enabled', '0') === '1'
            || get_option('aysa_agent8v3_image_optimization_site_gate_enabled', '0') === '1';
        $bridge_auth_ready = $this->get_app_aysa_internal_secret() !== '';
        $kill_switch = $this->aysa_agent8v3_env_flag('AYSA_AGENT8V3_TECHNICAL_APPLY_KILL_SWITCH')
            || $this->aysa_agent8v3_env_flag('AYSA_AGENT8V3_IMAGE_OPTIMIZATION_APPLY_KILL_SWITCH');
        return array(
            'module' => 'image_optimization',
            'project_gate_enabled' => $project_gate,
            'api_project_gate_enabled' => $project_gate,
            'site_gate_enabled' => $site_gate,
            'plugin_site_gate_enabled' => $site_gate,
            'bridge_deployed' => true,
            'bridge_auth_ready' => $bridge_auth_ready,
            'global_kill_switch_active' => $kill_switch,
            'normal_flow_enabled' => $project_gate && $site_gate && $bridge_auth_ready && !$kill_switch,
            'default_state' => 'off',
            'source' => 'plugin_admin_options',
        );
    }

    public function handle_agent8v3_image_optimization_product_gates_set() {
        if (!$this->aysa_agent8v3_image_alt_test_authorized()) {
            wp_send_json_error(array('message' => 'Unauthorized'), 403);
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_gate = !empty($_POST['project_gate_enabled']) && sanitize_text_field((string) wp_unslash($_POST['project_gate_enabled'])) !== '0';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $site_gate = !empty($_POST['site_gate_enabled']) && sanitize_text_field((string) wp_unslash($_POST['site_gate_enabled'])) !== '0';

        update_option('aysa_agent8v3_image_optimization_project_gate_enabled', $project_gate ? '1' : '0', false);
        update_option('aysa_agent8v3_image_optimization_site_gate_enabled', $site_gate ? '1' : '0', false);
        update_option('aysa_agent8v3_image_optimization_apply_enabled', $site_gate ? '1' : '0', false);

        wp_send_json_success(array(
            'product_gates' => $this->aysa_agent8v3_image_optimization_product_gates(),
            'billing_status' => 'image_optimization_real_billing',
            'billing_unit_cents' => 5,
        ));
    }

    private function aysa_agent8v3_image_optimization_crawl_scoped_candidates_rows($project_id) {
        $project_id = intval($project_id);
        $snapshot = $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id);
        $urls = array();
        foreach (array('pages', 'urls', 'crawled_urls', 'rows') as $key) {
            if (!empty($snapshot[$key]) && is_array($snapshot[$key])) {
                foreach ($snapshot[$key] as $row) {
                    if (!is_array($row)) {
                        continue;
                    }
                    $url = esc_url_raw((string) ($row['url'] ?? $row['page_url'] ?? $row['address'] ?? $row['page'] ?? ''));
                    if ($url !== '') {
                        $urls[$url] = true;
                    }
                    if (count($urls) >= 40) {
                        break 2;
                    }
                }
            }
        }
        if ($urls === array()) {
            $home = home_url('/');
            if ($home) {
                $urls[esc_url_raw($home)] = true;
            }
        }

        $rows = array();
        $seen = array();
        foreach (array_keys($urls) as $page_url) {
            $post_id = url_to_postid($page_url);
            if ($post_id <= 0) {
                continue;
            }
            $attachment_ids = array();
            $thumbnail_id = intval(get_post_thumbnail_id($post_id));
            if ($thumbnail_id > 0) {
                $attachment_ids[$thumbnail_id] = true;
            }
            $content = (string) get_post_field('post_content', $post_id);
            if ($content !== '') {
                if (preg_match_all('/wp-image-([0-9]+)/', $content, $matches)) {
                    foreach ((array) ($matches[1] ?? array()) as $match) {
                        $attachment_ids[intval($match)] = true;
                    }
                }
                if (preg_match_all('/<img[^>]+src=["\']([^"\']+)["\']/i', $content, $matches)) {
                    foreach ((array) ($matches[1] ?? array()) as $src) {
                        $attachment_id = attachment_url_to_postid(esc_url_raw((string) $src));
                        if ($attachment_id > 0) {
                            $attachment_ids[$attachment_id] = true;
                        }
                    }
                }
            }
            foreach ($this->aysa_agent8v3_image_optimization_attachment_ids_from_local_post($post_id) as $attachment_id) {
                $attachment_ids[intval($attachment_id)] = true;
            }

            foreach (array_keys($attachment_ids) as $attachment_id) {
                $attachment_id = intval($attachment_id);
                if ($attachment_id <= 0 || isset($seen[$attachment_id])) {
                    continue;
                }
                $candidate = $this->aysa_agent8v3_image_optimization_candidate_row($project_id, $attachment_id, $page_url, $post_id);
                if ($candidate === array()) {
                    continue;
                }
                $seen[$attachment_id] = true;
                $rows[] = $candidate;
                if (count($rows) >= 50) {
                    break 2;
                }
            }
        }
        return $rows;
    }

    private function aysa_agent8v3_image_optimization_attachment_ids_from_local_post($post_id) {
        $post_id = intval($post_id);
        if ($post_id <= 0) {
            return array();
        }
        $ids = array();
        $product_gallery = (string) get_post_meta($post_id, '_product_image_gallery', true);
        foreach (array_filter(array_map('intval', explode(',', $product_gallery))) as $attachment_id) {
            $ids[intval($attachment_id)] = true;
        }

        $elementor_data = (string) get_post_meta($post_id, '_elementor_data', true);
        if ($elementor_data !== '') {
            if (preg_match_all('/["\']id["\']\s*:\s*["\']?([0-9]+)["\']?\s*,\s*["\']url["\']\s*:\s*["\']([^"\']+\/uploads\/[^"\']+)["\']/i', $elementor_data, $matches, PREG_SET_ORDER)) {
                foreach ($matches as $match) {
                    $attachment_id = intval($match[1] ?? 0);
                    if ($attachment_id > 0) {
                        $ids[$attachment_id] = true;
                    }
                }
            }
            foreach ($this->aysa_agent8v3_image_optimization_image_urls_from_html($elementor_data, home_url('/')) as $image_url) {
                $attachment_id = attachment_url_to_postid($image_url);
                if ($attachment_id <= 0) {
                    $attachment_id = attachment_url_to_postid($this->aysa_agent8v3_image_optimization_original_url_from_size_url($image_url));
                }
                if ($attachment_id > 0) {
                    $ids[intval($attachment_id)] = true;
                }
            }
        }
        return array_slice(array_keys($ids), 0, 20);
    }

    private function aysa_agent8v3_image_optimization_image_urls_from_html($html, $base_url) {
        $html = (string) $html;
        $base_url = esc_url_raw((string) $base_url);
        $urls = array();
        if (preg_match_all('/(?:src|data-src|data-lazy-src|srcset|data-srcset)=["\']([^"\']+)["\']/i', $html, $matches)) {
            foreach ((array) ($matches[1] ?? array()) as $value) {
                foreach (explode(',', (string) $value) as $part) {
                    $raw = trim((string) preg_replace('/\s+\d+[wx]$/', '', trim((string) $part)));
                    if ($raw === '' || strpos($raw, 'data:') === 0) {
                        continue;
                    }
                    $absolute = $this->aysa_agent8v3_image_optimization_absolute_url($raw, $base_url);
                    if ($absolute === '') {
                        continue;
                    }
                    $absolute = strtok($absolute, '?');
                    if (!is_string($absolute) || $absolute === '') {
                        continue;
                    }
                    if (preg_match('#^' . preg_quote(content_url('/uploads/'), '#') . '.+\.(jpe?g|png)$#i', $absolute)) {
                        $urls[$absolute] = true;
                    }
                    if (count($urls) >= 40) {
                        break 2;
                    }
                }
            }
        }
        return array_keys($urls);
    }

    private function aysa_agent8v3_image_optimization_absolute_url($url, $base_url) {
        $url = trim((string) $url);
        if ($url === '') {
            return '';
        }
        if (strpos($url, '//') === 0) {
            return esc_url_raw((is_ssl() ? 'https:' : 'http:') . $url);
        }
        if (preg_match('#^https?://#i', $url)) {
            return esc_url_raw($url);
        }
        if (strpos($url, '/') === 0) {
            return esc_url_raw(home_url($url));
        }
        $base = trailingslashit((string) preg_replace('#/[^/]*$#', '/', $base_url));
        return esc_url_raw($base . ltrim($url, '/'));
    }

    private function aysa_agent8v3_image_optimization_original_url_from_size_url($url) {
        $url = esc_url_raw((string) $url);
        if ($url === '') {
            return '';
        }
        return preg_replace('/-\d+x\d+(\.(?:jpe?g|png))$/i', '$1', $url);
    }

    private function aysa_agent8v3_image_optimization_candidate_row($project_id, $attachment_id, $page_url, $post_id = 0) {
        $attachment_id = intval($attachment_id);
        $file = get_attached_file($attachment_id);
        $file = is_string($file) ? $file : '';
        if ($attachment_id <= 0 || $file === '' || !file_exists($file) || !is_readable($file)) {
            return array();
        }
        $mime = (string) get_post_mime_type($attachment_id);
        if ($mime === 'image/jpg') {
            $mime = 'image/jpeg';
        }
        if (!in_array($mime, array('image/jpeg', 'image/png'), true)) {
            return array();
        }
        $bytes = filesize($file);
        if ($bytes === false || intval($bytes) < 10240) {
            return array();
        }
        $existing = get_post_meta($attachment_id, '_aysa_agent8v3_image_optimization_webp', true);
        $already = is_array($existing)
            && $this->aysa_agent8v3_image_optimization_valid_existing_path((string) ($existing['webp_path'] ?? '')) !== '';
        if ($already) {
            return array();
        }
        $url = wp_get_attachment_url($attachment_id);
        $url = is_string($url) ? esc_url_raw($url) : '';
        if ($url === '') {
            return array();
        }
        $hash = substr(hash('sha256', $attachment_id . '|' . $url . '|' . $page_url), 0, 24);
        return array(
            'row_id' => 'image_optimization_preview_' . $hash,
            'attachment_id' => $attachment_id,
            'media_id' => $attachment_id,
            'post_id' => intval($post_id),
            'original_url' => $url,
            'image_url' => $url,
            'source_page_url' => esc_url_raw((string) $page_url),
            'source_pages_sample' => array(esc_url_raw((string) $page_url)),
            'usage_evidence' => 'crawl_scoped_usage',
            'mime_type' => $mime,
            'original_bytes' => intval($bytes),
            'estimated_savings_bytes' => intval(round(intval($bytes) * 0.25)),
            'already_optimized' => false,
            'apply_readiness' => 'ready',
            'reason' => 'crawl_scoped_original_jpg_png',
            'confidence' => 90,
            'source' => 'plugin_crawl_scoped_image_optimization_candidates',
        );
    }

    private function aysa_agent8v3_technical_image_optimization_apply_bridge($project_id, $payload) {
        $selected_items = is_array($payload['selected_items'] ?? null) ? array_values($payload['selected_items']) : array();
        $idempotency_key = sanitize_text_field((string) ($payload['idempotency_key'] ?? ''));
        $payload_hash = sanitize_text_field((string) ($payload['payload_hash'] ?? ''));
        $result = array(
            'operation' => 'technical_image_optimization_apply_bridge',
            'module' => 'image_optimization',
            'project_id' => intval($project_id),
            'payload_hash' => $payload_hash,
            'idempotency_key' => $idempotency_key,
            'accepted_count' => count($selected_items),
            'applied_count' => 0,
            'verified_count' => 0,
            'failed_count' => 0,
            'skipped_count' => 0,
            'manual_needed_count' => 0,
            'item_results' => array(),
            'dry_run' => !empty($payload['dry_run']),
            'writes_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'billing_action_key' => 'agent8v3_image_optimization_apply',
            'billing_cost_cents' => 0,
            'actual_credits_cents' => 0,
        );
        $billing_status = sanitize_key((string) ($payload['billing_status'] ?? ''));
        $payload_product_gates = isset($payload['product_gates']) && is_array($payload['product_gates']) ? $payload['product_gates'] : array();
        $local_product_gates = $this->aysa_agent8v3_image_optimization_product_gates();
        $project_gate_enabled = !empty($payload_product_gates['project_gate_enabled']) && !empty($local_product_gates['project_gate_enabled']);
        $site_gate_enabled = !empty($payload_product_gates['site_gate_enabled']) && !empty($local_product_gates['site_gate_enabled']);
        $kill_switch_active = !empty($local_product_gates['global_kill_switch_active']) || !empty($payload_product_gates['global_kill_switch_active']);
        $result['billing_status'] = $billing_status === 'image_optimization_real_billing' ? 'image_optimization_real_billing' : 'image_optimization_real_billing_blocked';
        $result['billing_policy'] = 'verified_optimized_images_only_cost_x5';
        $result['product_gates'] = array(
            'payload' => $payload_product_gates,
            'local' => $local_product_gates,
            'project_gate_enabled' => $project_gate_enabled,
            'site_gate_enabled' => $site_gate_enabled,
            'global_kill_switch_active' => $kill_switch_active,
        );
        $gate_reasons = array();
        if (sanitize_key((string) ($payload['module'] ?? '')) !== 'image_optimization') {
            $gate_reasons[] = 'module_not_image_optimization';
        }
        if (empty($payload['user_confirmed'])) {
            $gate_reasons[] = 'user_confirmation_required';
        }
        if ($selected_items === array()) {
            $gate_reasons[] = 'selected_image_optimization_rows_required';
        }
        if ($idempotency_key === '') {
            $gate_reasons[] = 'idempotency_key_required';
        }
        if (!$project_gate_enabled) {
            $gate_reasons[] = 'image_optimization_project_gate_disabled';
        }
        if (!$site_gate_enabled) {
            $gate_reasons[] = 'image_optimization_site_gate_disabled';
        }
        if ($kill_switch_active) {
            $gate_reasons[] = 'image_optimization_global_kill_switch_active';
        }
        if ($billing_status !== 'image_optimization_real_billing') {
            $gate_reasons[] = 'image_optimization_real_billing_required';
        }
        if (sanitize_key((string) ($payload['billing_action_key'] ?? '')) !== 'agent8v3_image_optimization_apply') {
            $gate_reasons[] = 'image_optimization_billing_action_required';
        }
        if (!empty($payload['dry_run']) || empty($payload['writes_enabled'])) {
            $gate_reasons[] = 'writes_not_requested';
        }
        if ($gate_reasons !== array()) {
            $result['status'] = $selected_items === array() ? 'no_executable_fixes' : 'blocked';
            $result['reason'] = $gate_reasons[0];
            $result['gate_reasons'] = array_values(array_unique($gate_reasons));
            return $result;
        }

        $unit_cost_cents = max(1, intval($payload['billing_unit_cents'] ?? 5));
        $max_cost_cents = max(count($selected_items) * $unit_cost_cents, intval($payload['max_cost_cents'] ?? 0));
        $billing_preflight = $this->aysa_agent8v3_image_alt_test_billing_preflight($max_cost_cents, $idempotency_key, 'image_optimization_agent8_flow');
        $result['billing_preflight'] = $billing_preflight;
        if (empty($billing_preflight['ok'])) {
            $result['status'] = 'blocked';
            $result['reason'] = 'image_optimization_billing_preflight_failed';
            $result['gate_reasons'] = array('image_optimization_billing_preflight_failed');
            $result['billing_status'] = 'billing_preflight_failed';
            return $result;
        }

        $items_hash = hash('sha256', wp_json_encode($selected_items));
        $records = get_option('aysa_agent8v3_image_optimization_apply_idempotency', array());
        $records = is_array($records) ? $records : array();
        if (isset($records[$idempotency_key]) && is_array($records[$idempotency_key])) {
            $record = $records[$idempotency_key];
            if (sanitize_text_field((string) ($record['items_hash'] ?? '')) !== $items_hash) {
                $result['status'] = 'idempotency_conflict';
                $result['reason'] = 'idempotency_key_payload_mismatch';
                return $result;
            }
            $replay = is_array($record['result'] ?? null) ? $record['result'] : array();
            $replay['idempotent_replay'] = true;
            return array_merge($result, $replay);
        }

        $context = array(
            'project_id' => intval($project_id),
            'site_host' => $this->aysa_agent8v3_site_host(),
            'idempotency_key' => $idempotency_key,
            'payload_hash' => $payload_hash,
            'source_run_id' => intval($payload['parent_technical_run_id'] ?? $payload['run_id'] ?? 0),
        );
        foreach ($selected_items as $item) {
            $item_result = $this->aysa_agent8v3_apply_single_image_optimization_item($item, $context);
            $result['item_results'][] = $item_result;
            $status = sanitize_key((string) ($item_result['status'] ?? 'failed'));
            if ($status === 'verified') {
                $result['verified_count']++;
                $result['applied_count']++;
            } elseif (str_starts_with($status, 'skipped')) {
                $result['skipped_count']++;
            } elseif ($status === 'manual_review_needed') {
                $result['manual_needed_count']++;
            } else {
                $result['failed_count']++;
            }
        }
        if ($result['verified_count'] > 0 && $result['failed_count'] === 0 && $result['manual_needed_count'] === 0) {
            $result['status'] = 'verified';
        } elseif ($result['verified_count'] > 0) {
            $result['status'] = 'partial_failed';
        } elseif ($result['skipped_count'] > 0 && $result['failed_count'] === 0 && $result['manual_needed_count'] === 0) {
            $result['status'] = 'skipped';
        } elseif ($result['manual_needed_count'] > 0 && $result['failed_count'] === 0) {
            $result['status'] = 'manual_review_needed';
        } else {
            $result['status'] = 'failed';
        }
        $result['writes_enabled'] = true;
        $result['completed_at'] = current_time('mysql');
        $billing_result = $this->aysa_agent8v3_bill_image_optimization_apply_result($project_id, $payload, $result);
        $this->aysa_agent8v3_update_image_optimization_history_billing(is_array($result['item_results'] ?? null) ? $result['item_results'] : array(), $billing_result);
        $result['billing'] = $billing_result;
        $result['billing_status'] = sanitize_key((string) ($billing_result['status'] ?? 'billing_not_applicable'));
        $result['billing_cost_cents'] = intval($billing_result['cost_cents'] ?? 0);
        $result['actual_credits_cents'] = intval($billing_result['cost_cents'] ?? 0);
        $result['billing_external_recorded'] = in_array($result['billing_status'], array('billed', 'idempotent_replay', 'not_billable_no_verified_changes'), true);
        $result['billing_reference'] = sanitize_text_field((string) ($billing_result['reference'] ?? ''));

        $records[$idempotency_key] = array('items_hash' => $items_hash, 'result' => $result, 'timestamp' => current_time('mysql'));
        update_option('aysa_agent8v3_image_optimization_apply_idempotency', array_slice($records, -100, null, true), false);
        return $result;
    }

    private function aysa_agent8v3_apply_single_image_optimization_item($item, $context = array()) {
        $item = is_array($item) ? $item : array();
        $context = is_array($context) ? $context : array();
        $attachment_id = intval($item['attachment_id'] ?? $item['media_id'] ?? 0);
        $source_page_url = esc_url_raw((string) ($item['source_page_url'] ?? ''));
        $mime = sanitize_text_field((string) ($item['mime_type'] ?? ''));
        if ($mime === 'image/jpg') {
            $mime = 'image/jpeg';
        }
        $item_result = array(
            'item_id' => sanitize_text_field((string) ($item['item_id'] ?? $item['row_id'] ?? $attachment_id . '_webp')),
            'attachment_id' => $attachment_id,
            'original_url' => esc_url_raw((string) ($item['original_url'] ?? $item['image_url'] ?? '')),
            'source_page_url' => $source_page_url,
            'mime_type' => $mime,
            'changed' => false,
            'verification_status' => 'not_verified',
        );
        if (empty($item['accepted_by_user'])) {
            return array_merge($item_result, array('status' => 'skipped_not_accepted', 'skipped_reason' => 'item_not_accepted'));
        }
        if ($attachment_id <= 0 || get_post_type($attachment_id) !== 'attachment') {
            return array_merge($item_result, array('status' => 'skipped_missing_item', 'skipped_reason' => 'attachment_missing'));
        }
        if ($source_page_url === '') {
            return array_merge($item_result, array('status' => 'manual_review_needed', 'skipped_reason' => 'crawl_usage_evidence_required'));
        }
        if (!in_array($mime, array('image/jpeg', 'image/png'), true)) {
            return array_merge($item_result, array('status' => 'manual_review_needed', 'skipped_reason' => 'unsupported_mime_type'));
        }
        $file = get_attached_file($attachment_id);
        $file = is_string($file) ? $file : '';
        if ($file === '' || !file_exists($file) || !is_readable($file)) {
            return array_merge($item_result, array('status' => 'skipped_missing_file', 'skipped_reason' => 'attachment_file_missing'));
        }
        $original_bytes = filesize($file);
        if ($original_bytes === false || intval($original_bytes) < 10240) {
            return array_merge($item_result, array('status' => 'skipped_too_small', 'skipped_reason' => 'image_too_small'));
        }
        $existing = get_post_meta($attachment_id, '_aysa_agent8v3_image_optimization_webp', true);
        $existing_webp_path = is_array($existing)
            ? $this->aysa_agent8v3_image_optimization_valid_existing_path((string) ($existing['webp_path'] ?? ''))
            : '';
        if ($existing_webp_path !== '') {
            return array_merge($item_result, array(
                'status' => 'skipped_no_change',
                'changed' => false,
                'verification_status' => 'verified_no_change',
                'webp_url' => esc_url_raw((string) ($existing['webp_url'] ?? '')),
                'skipped_reason' => 'webp_already_exists',
            ));
        }

        $uploads = wp_get_upload_dir();
        $base_dir = trailingslashit((string) ($uploads['basedir'] ?? ''));
        $base_url = trailingslashit((string) ($uploads['baseurl'] ?? ''));
        if ($base_dir === '' || $base_url === '') {
            return array_merge($item_result, array('status' => 'failed', 'error_code' => 'uploads_dir_unavailable'));
        }
        $dir = dirname($file);
        $info = pathinfo($file);
        $target = $this->aysa_image_optimization_unique_path($dir, sanitize_file_name((string) ($info['filename'] ?? 'aysa-image')) . '-aysa.webp');
        $history_id = $this->aysa_agent8v3_create_image_optimization_history_row($item, $context, $file, intval($original_bytes), $target);
        $convert = $this->aysa_image_optimization_convert_to_webp($file, $target, $mime);
        if (empty($convert['ok'])) {
            $this->aysa_agent8v3_update_image_optimization_history_row($history_id, array('status' => 'failed', 'verification_status' => 'failed', 'error_code' => sanitize_key((string) ($convert['reason'] ?? 'webp_conversion_failed'))));
            return array_merge($item_result, array('status' => 'failed', 'history_id' => $history_id, 'error_code' => sanitize_key((string) ($convert['reason'] ?? 'webp_conversion_failed'))));
        }
        $webp_bytes = file_exists($target) ? filesize($target) : 0;
        $rel = ltrim(str_replace($base_dir, '', $target), '/');
        $webp_url = esc_url_raw($base_url . str_replace(DIRECTORY_SEPARATOR, '/', $rel));
        $record = array(
            'status' => 'optimized',
            'source' => 'agent8v3_image_optimization_apply',
            'project_id' => intval($context['project_id'] ?? 0),
            'attachment_id' => $attachment_id,
            'original_url' => esc_url_raw((string) ($item['original_url'] ?? $item_result['original_url'] ?? '')),
            'source_page_url' => $source_page_url,
            'webp_url' => $webp_url,
            'webp_path' => $target,
            'original_bytes' => intval($original_bytes),
            'webp_bytes' => intval($webp_bytes),
            'generated_at' => current_time('mysql'),
            'idempotency_key' => sanitize_text_field((string) ($context['idempotency_key'] ?? '')),
        );
        update_post_meta($attachment_id, '_aysa_agent8v3_image_optimization_webp', $record);
        update_post_meta($attachment_id, '_aysa_agent8v3_image_optimization_updated_at', current_time('mysql'));
        $verified = file_exists($target) && intval($webp_bytes) > 0 && file_exists($file) && get_attached_file($attachment_id) === $file && (string) get_post_mime_type($attachment_id) === $mime;
        if ($verified) {
            $this->aysa_agent8v3_update_image_optimization_history_row($history_id, array('status' => 'changed', 'actual_value' => $webp_url, 'verification_status' => 'verified'));
            return array_merge($item_result, array(
                'status' => 'verified',
                'changed' => true,
                'verification_status' => 'verified',
                'history_id' => $history_id,
                'webp_url' => $webp_url,
                'original_bytes' => intval($original_bytes),
                'webp_bytes' => intval($webp_bytes),
            ));
        }
        $this->aysa_agent8v3_update_image_optimization_history_row($history_id, array('status' => 'failed', 'actual_value' => $webp_url, 'verification_status' => 'failed', 'error_code' => 'readback_verification_failed'));
        return array_merge($item_result, array('status' => 'failed', 'changed' => true, 'history_id' => $history_id, 'error_code' => 'readback_verification_failed', 'webp_url' => $webp_url));
    }

    private function aysa_agent8v3_image_optimization_history_rows() {
        $rows = get_option('aysa_agent8v3_image_optimization_apply_history', array());
        return is_array($rows) ? array_values(array_filter($rows, 'is_array')) : array();
    }

    private function aysa_agent8v3_image_optimization_valid_existing_path($path) {
        $path = wp_normalize_path(trim((string) $path));
        $uploads = wp_upload_dir(null, false);
        $base_dir = wp_normalize_path((string) ($uploads['basedir'] ?? ''));
        if ($path === '' || $base_dir === '') {
            return '';
        }
        $base_prefix = trailingslashit($base_dir);
        if (!str_starts_with($path, $base_prefix)) {
            return '';
        }
        $real_base = realpath($base_dir);
        $real_path = realpath($path);
        if ($real_base === false || $real_path === false || !is_file($real_path)) {
            return '';
        }
        $real_base_prefix = trailingslashit(wp_normalize_path((string) $real_base));
        $normalized_real_path = wp_normalize_path((string) $real_path);
        return str_starts_with($normalized_real_path, $real_base_prefix) ? $normalized_real_path : '';
    }

    private function aysa_agent8v3_save_image_optimization_history_rows($rows) {
        $rows = is_array($rows) ? array_values(array_filter($rows, 'is_array')) : array();
        update_option('aysa_agent8v3_image_optimization_apply_history', array_slice($rows, -200), false);
    }

    private function aysa_agent8v3_create_image_optimization_history_row($item, $context, $original_path, $original_bytes, $target_path) {
        $history_id = 'ioh_' . time() . '_' . wp_generate_password(8, false, false);
        $row = array(
            'source' => 'agent8v3_image_optimization_apply',
            'history_id' => $history_id,
            'project_id' => intval($context['project_id'] ?? 0),
            'site_host' => sanitize_text_field((string) ($context['site_host'] ?? $this->aysa_agent8v3_site_host())),
            'attachment_id' => intval($item['attachment_id'] ?? $item['media_id'] ?? 0),
            'original_url' => esc_url_raw((string) ($item['original_url'] ?? $item['image_url'] ?? '')),
            'source_page_url' => esc_url_raw((string) ($item['source_page_url'] ?? '')),
            'old_value' => basename((string) $original_path),
            'new_value' => basename((string) $target_path),
            'webp_path' => (string) $target_path,
            'actual_value' => '',
            'original_bytes' => intval($original_bytes),
            'source_run_id' => intval($context['source_run_id'] ?? 0),
            'payload_hash' => sanitize_text_field((string) ($context['payload_hash'] ?? '')),
            'idempotency_key' => sanitize_text_field((string) ($context['idempotency_key'] ?? '')),
            'timestamp' => current_time('mysql'),
            'updated_at' => current_time('mysql'),
            'billing_action_key' => 'agent8v3_image_optimization_apply',
            'billing_status' => 'pending_result',
            'billing_cost_cents' => 0,
            'verification_status' => 'pending',
            'status' => 'submitted',
            'restore_available' => true,
            'restored_at' => '',
            'error_code' => '',
        );
        $rows = $this->aysa_agent8v3_image_optimization_history_rows();
        $rows[] = $row;
        $this->aysa_agent8v3_save_image_optimization_history_rows($rows);
        return $history_id;
    }

    private function aysa_agent8v3_update_image_optimization_history_row($history_id, $updates) {
        $history_id = sanitize_text_field((string) $history_id);
        if ($history_id === '') {
            return;
        }
        $rows = $this->aysa_agent8v3_image_optimization_history_rows();
        foreach ($rows as $index => $row) {
            if (sanitize_text_field((string) ($row['history_id'] ?? '')) !== $history_id) {
                continue;
            }
            foreach ((array) $updates as $key => $value) {
                $safe_key = sanitize_key((string) $key);
                if ($safe_key === '') {
                    continue;
                }
                if (in_array($safe_key, array('billing_cost_cents', 'attachment_id', 'project_id', 'source_run_id', 'original_bytes'), true)) {
                    $row[$safe_key] = intval($value);
                } elseif (in_array($safe_key, array('actual_value', 'restored_value', 'original_url', 'source_page_url'), true)) {
                    $row[$safe_key] = esc_url_raw((string) $value);
                } else {
                    $row[$safe_key] = sanitize_text_field((string) $value);
                }
            }
            $row['updated_at'] = current_time('mysql');
            $rows[$index] = $row;
            $this->aysa_agent8v3_save_image_optimization_history_rows($rows);
            return;
        }
    }

    private function aysa_agent8v3_update_image_optimization_history_billing($item_results, $billing_result) {
        $unit_cost = intval($billing_result['unit_cost_cents'] ?? 5);
        $billing_status = sanitize_key((string) ($billing_result['status'] ?? 'not_billed'));
        foreach ((array) $item_results as $item_result) {
            if (!is_array($item_result) || empty($item_result['history_id'])) {
                continue;
            }
            $cost = (sanitize_key((string) ($item_result['status'] ?? '')) === 'verified' && !empty($item_result['changed']) && in_array($billing_status, array('billed', 'idempotent_replay'), true)) ? $unit_cost : 0;
            $this->aysa_agent8v3_update_image_optimization_history_row($item_result['history_id'], array(
                'billing_status' => $cost > 0 ? $billing_status : 'not_billable',
                'billing_cost_cents' => $cost,
            ));
        }
    }

    private function aysa_agent8v3_bill_image_optimization_apply_result($project_id, $payload, $result) {
        $idempotency_key = sanitize_text_field((string) ($payload['idempotency_key'] ?? ''));
        $verified_changed = 0;
        $attachment_ids = array();
        foreach ((array) ($result['item_results'] ?? array()) as $item) {
            if (is_array($item) && (string) ($item['status'] ?? '') === 'verified' && !empty($item['changed'])) {
                $verified_changed++;
                $attachment_ids[] = intval($item['attachment_id'] ?? 0);
            }
        }
        $unit_cost_cents = max(1, intval($payload['billing_unit_cents'] ?? 5));
        $cost_cents = $verified_changed * $unit_cost_cents;
        $base = array(
            'status' => $cost_cents > 0 ? 'pending' : 'not_billable_no_verified_changes',
            'unit_cost_cents' => $unit_cost_cents,
            'verified_changed_count' => $verified_changed,
            'cost_cents' => $cost_cents,
            'idempotency_key' => $idempotency_key,
        );
        if (intval($project_id) <= 0 || $idempotency_key === '' || $cost_cents <= 0) {
            return $base;
        }
        $records = get_option('aysa_agent8v3_image_optimization_apply_billing_idempotency', array());
        $records = is_array($records) ? $records : array();
        $billing_key = hash('sha256', 'image_optimization_apply_billing|' . intval($project_id) . '|' . $idempotency_key);
        if (isset($records[$billing_key]) && is_array($records[$billing_key])) {
            return array_merge($base, $records[$billing_key], array('status' => 'idempotent_replay'));
        }
        $wallet = $this->aysa_agent8v3_image_alt_test_wallet_manager();
        if (!$wallet || !method_exists($wallet, 'track_action_usage')) {
            return array_merge($base, array('status' => 'billing_failed', 'error' => 'wallet_unavailable'));
        }
        $reference = 'agent8v3_image_optimization_apply:' . substr($idempotency_key, -24);
        $ok = $wallet->track_action_usage(
            intval($project_id),
            'agent8v3_image_optimization_apply',
            $reference,
            array(
                'billing_policy' => 'verified_optimized_images_only_cost_x5',
                'billing_context' => 'normal_agent8_flow',
                'billing_idempotency_key' => $billing_key,
                'payload_hash' => sanitize_text_field((string) ($payload['payload_hash'] ?? '')),
                'verified_changed_count' => $verified_changed,
                'unit_cost_cents' => $unit_cost_cents,
                'override_cost_cents' => $cost_cents,
                'attachment_ids' => array_values(array_filter(array_map('intval', $attachment_ids))),
            ),
            $cost_cents,
            'action:agent8v3_image_optimization_apply'
        );
        if (!$ok) {
            return array_merge($base, array('status' => 'billing_failed', 'error' => 'wallet_debit_failed'));
        }
        $record = array(
            'status' => 'billed',
            'cost_cents' => $cost_cents,
            'unit_cost_cents' => $unit_cost_cents,
            'verified_changed_count' => $verified_changed,
            'reference' => $reference,
            'action_key' => 'agent8v3_image_optimization_apply',
            'billed_at' => current_time('mysql'),
        );
        $records[$billing_key] = $record;
        update_option('aysa_agent8v3_image_optimization_apply_billing_idempotency', array_slice($records, -100, null, true), false);
        return array_merge($base, $record);
    }

    private function aysa_agent8v3_technical_image_optimization_restore_bridge($project_id, $payload) {
        $history_id = sanitize_text_field((string) ($payload['history_id'] ?? ''));
        $result = array(
            'operation' => 'technical_image_optimization_restore_bridge',
            'module' => 'image_optimization',
            'project_id' => intval($project_id),
            'history_id' => $history_id,
            'billing_status' => 'not_billable_restore',
            'billing_cost_cents' => 0,
            'actual_credits_cents' => 0,
            'writes_enabled' => false,
        );
        if ($history_id === '') {
            return array_merge($result, array('status' => 'failed', 'reason' => 'history_id_required'));
        }
        $uploads = wp_get_upload_dir();
        $base_dir = realpath((string) ($uploads['basedir'] ?? ''));
        if (!$base_dir) {
            return array_merge($result, array('status' => 'failed', 'reason' => 'uploads_dir_unavailable'));
        }
        $rows = $this->aysa_agent8v3_image_optimization_history_rows();
        foreach ($rows as $row) {
            if (sanitize_text_field((string) ($row['history_id'] ?? '')) !== $history_id) {
                continue;
            }
            $attachment_id = intval($row['attachment_id'] ?? 0);
            $webp_path = (string) ($row['webp_path'] ?? '');
            $real_webp_path = $webp_path !== '' ? realpath($webp_path) : false;
            if ($attachment_id <= 0 || !$real_webp_path || !str_starts_with((string) $real_webp_path, $base_dir)) {
                return array_merge($result, array('status' => 'failed', 'reason' => 'restore_target_invalid'));
            }
            $deleted = !file_exists((string) $real_webp_path) || wp_delete_file((string) $real_webp_path);
            $existing = get_post_meta($attachment_id, '_aysa_agent8v3_image_optimization_webp', true);
            if (is_array($existing) && realpath((string) ($existing['webp_path'] ?? '')) === $real_webp_path) {
                delete_post_meta($attachment_id, '_aysa_agent8v3_image_optimization_webp');
            }
            $verified = $deleted && !file_exists((string) $real_webp_path);
            $this->aysa_agent8v3_update_image_optimization_history_row($history_id, array(
                'restored_at' => current_time('mysql'),
                'restore_status' => $verified ? 'verified' : 'failed',
                'restored_value' => $verified ? 'webp_derivative_removed' : 'webp_derivative_restore_failed',
            ));
            return array_merge($result, array(
                'status' => $verified ? 'verified' : 'failed',
                'reason' => $verified ? '' : 'webp_delete_failed',
                'writes_enabled' => true,
                'attachment_id' => $attachment_id,
                'completed_at' => current_time('mysql'),
            ));
        }
        return array_merge($result, array('status' => 'failed', 'reason' => 'history_not_found'));
    }

    private function aysa_agent8v3_internal_linking_product_gates() {
        $project_gate = get_option('aysa_agent8v3_internal_linking_project_gate_enabled', '0') === '1';
        $site_gate = get_option('aysa_agent8v3_internal_linking_apply_enabled', '0') === '1'
            || get_option('aysa_agent8v3_internal_linking_site_gate_enabled', '0') === '1';
        $kill_switch = $this->aysa_agent8v3_env_flag('AYSA_AGENT8V3_TECHNICAL_APPLY_KILL_SWITCH')
            || $this->aysa_agent8v3_env_flag('AYSA_AGENT8V3_INTERNAL_LINKING_APPLY_KILL_SWITCH');
        return array(
            'module' => 'internal_linking',
            'project_gate_enabled' => $project_gate,
            'api_project_gate_enabled' => $project_gate,
            'site_gate_enabled' => $site_gate,
            'plugin_site_gate_enabled' => $site_gate,
            'bridge_deployed' => true,
            'global_kill_switch_active' => $kill_switch,
            'normal_flow_enabled' => $project_gate && $site_gate && !$kill_switch,
            'default_state' => 'off',
            'source' => 'plugin_admin_options',
        );
    }

    public function handle_agent8v3_internal_linking_product_gates_set() {
        if (!$this->aysa_agent8v3_image_alt_test_authorized()) {
            wp_send_json_error(array('message' => 'Unauthorized'), 403);
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_gate = !empty($_POST['project_gate_enabled']) && sanitize_text_field((string) wp_unslash($_POST['project_gate_enabled'])) !== '0';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $site_gate = !empty($_POST['site_gate_enabled']) && sanitize_text_field((string) wp_unslash($_POST['site_gate_enabled'])) !== '0';

        update_option('aysa_agent8v3_internal_linking_project_gate_enabled', $project_gate ? '1' : '0', false);
        update_option('aysa_agent8v3_internal_linking_site_gate_enabled', $site_gate ? '1' : '0', false);
        update_option('aysa_agent8v3_internal_linking_apply_enabled', $site_gate ? '1' : '0', false);

        wp_send_json_success(array(
            'product_gates' => $this->aysa_agent8v3_internal_linking_product_gates(),
            'billing_status' => 'internal_linking_real_billing',
            'billing_unit_cents' => 5,
        ));
    }

    public function handle_agent8v3_internal_linking_rejection_action() {
        if (!$this->aysa_agent8v3_image_alt_test_authorized()) {
            wp_send_json_error(array('message' => 'Unauthorized'), 403);
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $operation = sanitize_key((string) wp_unslash($_POST['operation'] ?? ''));
        if (!in_array($operation, array('reject', 'restore'), true)) {
            wp_send_json_error(array('message' => 'invalid_internal_linking_rejection_operation'), 400);
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = intval($_POST['project_id'] ?? $this->aysa_resolve_project_id_with_local_fallback(0));
        $website_id = intval(get_option('aysa_website_id', 0));
        // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $row_payload_raw = isset($_POST['row_payload']) ? (string) wp_unslash($_POST['row_payload']) : '{}';
        $row_payload = json_decode($row_payload_raw, true);
        $row_payload = is_array($row_payload) ? $row_payload : array();
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $row_hash = sanitize_text_field((string) wp_unslash($_POST['row_hash'] ?? ''));
        if ($row_hash === '') {
            $row_hash = sanitize_text_field((string) ($row_payload['row_hash'] ?? $row_payload['payload_hash'] ?? $row_payload['item_id'] ?? $row_payload['row_id'] ?? ''));
        }
        if ($row_hash === '') {
            $row_hash = substr(hash('sha256', implode('|', array(
                (string) intval($row_payload['source_post_id'] ?? 0),
                (string) intval($row_payload['target_post_id'] ?? 0),
                sanitize_text_field((string) ($row_payload['anchor_text'] ?? $row_payload['proposed_anchor_text'] ?? '')),
            ))), 0, 32);
        }
        if ($row_hash === '') {
            wp_send_json_error(array('message' => 'internal_linking_row_hash_required'), 400);
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $reason = sanitize_key((string) wp_unslash($_POST['reason'] ?? 'other'));
        if (!in_array($reason, array('anchor_not_ok', 'sentence_not_natural', 'wrong_target', 'not_relevant', 'do_not_link_this_page', 'other'), true)) {
            $reason = 'other';
        }

        $rows = $this->aysa_agent8v3_internal_linking_rejection_rows();
        if ($operation === 'restore') {
            unset($rows[$row_hash]);
        } else {
            $rows[$row_hash] = array(
                'project_id' => $project_id,
                'website_id' => $website_id,
                'module' => 'internal_linking',
                'opportunity_id' => sanitize_text_field((string) ($row_payload['opportunity_id'] ?? $row_payload['item_id'] ?? $row_payload['row_id'] ?? '')),
                'source_post_id' => intval($row_payload['source_post_id'] ?? 0),
                'target_post_id' => intval($row_payload['target_post_id'] ?? 0),
                'anchor' => sanitize_text_field((string) ($row_payload['anchor_text'] ?? $row_payload['proposed_anchor_text'] ?? '')),
                'reason' => $reason,
                'rejected_by_user_id' => get_current_user_id(),
                'rejected_at' => current_time('mysql'),
                'row_hash' => $row_hash,
            );
        }
        update_option('aysa_agent8v3_internal_linking_rejected_rows', array_slice($rows, -250, null, true), false);
        wp_send_json_success(array(
            'status' => $operation === 'restore' ? 'restored' : 'rejected',
            'row_hash' => $row_hash,
            'rejected_count' => count($rows),
            'billing_status' => 'not_billable_review_feedback',
        ));
    }

    private function aysa_agent8v3_internal_linking_rejection_rows() {
        $rows = get_option('aysa_agent8v3_internal_linking_rejected_rows', array());
        return is_array($rows) ? $rows : array();
    }

    private function aysa_agent8v3_internal_linking_apply_bridge($project_id, $payload) {
        $selected_items = is_array($payload['selected_items'] ?? null) ? array_values($payload['selected_items']) : array();
        $rejected_rows = $this->aysa_agent8v3_internal_linking_rejection_rows();
        $skipped_rejected = array();
        if ($selected_items !== array() && $rejected_rows !== array()) {
            $selected_items = array_values(array_filter($selected_items, function($item) use ($rejected_rows, &$skipped_rejected) {
                $item = is_array($item) ? $item : array();
                $row_hash = sanitize_text_field((string) ($item['row_hash'] ?? $item['payload_hash'] ?? $item['item_id'] ?? $item['row_id'] ?? ''));
                if ($row_hash === '') {
                    $row_hash = substr(hash('sha256', implode('|', array(
                        (string) intval($item['source_post_id'] ?? 0),
                        (string) intval($item['target_post_id'] ?? 0),
                        sanitize_text_field((string) ($item['anchor_text'] ?? $item['proposed_anchor_text'] ?? '')),
                    ))), 0, 32);
                }
                if ($row_hash !== '' && isset($rejected_rows[$row_hash])) {
                    $skipped_rejected[] = array(
                        'item_id' => sanitize_text_field((string) ($item['item_id'] ?? $item['row_id'] ?? $row_hash)),
                        'row_hash' => $row_hash,
                        'source_post_id' => intval($item['source_post_id'] ?? 0),
                        'target_post_id' => intval($item['target_post_id'] ?? 0),
                        'status' => 'skipped_rejected',
                        'changed' => false,
                        'verification_status' => 'not_applicable',
                    );
                    return false;
                }
                return true;
            }));
        }
        $idempotency_key = sanitize_text_field((string) ($payload['idempotency_key'] ?? ''));
        $payload_hash = sanitize_text_field((string) ($payload['payload_hash'] ?? ''));
        $result = array(
            'operation' => 'internal_linking_apply_bridge',
            'module' => 'internal_linking',
            'project_id' => intval($project_id),
            'payload_hash' => $payload_hash,
            'idempotency_key' => $idempotency_key,
            'accepted_count' => count($selected_items),
            'applied_count' => 0,
            'verified_count' => 0,
            'failed_count' => 0,
            'skipped_count' => 0,
            'manual_needed_count' => 0,
            'item_results' => array(),
            'rejected_count' => intval($payload['rejected_count'] ?? count($skipped_rejected)),
            'dry_run' => !empty($payload['dry_run']),
            'writes_enabled' => false,
            'billing_status' => 'internal_linking_real_billing_blocked',
            'billing_action_key' => 'agent8v3_internal_linking_apply',
            'billing_policy' => 'verified_inserted_internal_links_only_cost_x5',
            'billing_cost_cents' => 0,
            'actual_credits_cents' => 0,
        );

        $billing_status = sanitize_key((string) ($payload['billing_status'] ?? ''));
        $payload_product_gates = isset($payload['product_gates']) && is_array($payload['product_gates']) ? $payload['product_gates'] : array();
        $local_product_gates = $this->aysa_agent8v3_internal_linking_product_gates();
        $project_gate_enabled = !empty($payload_product_gates['project_gate_enabled']) && !empty($local_product_gates['project_gate_enabled']);
        $site_gate_enabled = !empty($payload_product_gates['site_gate_enabled']) && !empty($local_product_gates['site_gate_enabled']);
        $kill_switch_active = !empty($local_product_gates['global_kill_switch_active']) || !empty($payload_product_gates['global_kill_switch_active']);
        $result['product_gates'] = array(
            'payload' => $payload_product_gates,
            'local' => $local_product_gates,
            'project_gate_enabled' => $project_gate_enabled,
            'site_gate_enabled' => $site_gate_enabled,
            'bridge_deployed' => !empty($local_product_gates['bridge_deployed']),
            'global_kill_switch_active' => $kill_switch_active,
        );

        $gate_reasons = array();
        if (sanitize_key((string) ($payload['module'] ?? '')) !== 'internal_linking') {
            $gate_reasons[] = 'module_not_internal_linking';
        }
        if (empty($payload['user_confirmed'])) {
            $gate_reasons[] = 'user_confirmation_required';
        }
        if ($selected_items === array()) {
            $gate_reasons[] = 'selected_internal_linking_rows_required';
        }
        if (count($selected_items) > 1 && sanitize_text_field((string) ($payload['batch_idempotency_key'] ?? '')) === '') {
            $gate_reasons[] = 'internal_linking_batch_idempotency_required';
        }
        if ($idempotency_key === '') {
            $gate_reasons[] = 'idempotency_key_required';
        }
        if (!$project_gate_enabled) {
            $gate_reasons[] = 'internal_linking_project_gate_disabled';
        }
        if (!$site_gate_enabled) {
            $gate_reasons[] = 'internal_linking_site_gate_disabled';
        }
        if ($kill_switch_active) {
            $gate_reasons[] = 'internal_linking_global_kill_switch_active';
        }
        if ($billing_status !== 'internal_linking_real_billing') {
            $gate_reasons[] = 'internal_linking_real_billing_required';
        }
        if (sanitize_key((string) ($payload['billing_action_key'] ?? '')) !== 'agent8v3_internal_linking_apply') {
            $gate_reasons[] = 'internal_linking_billing_action_required';
        }
        foreach ($selected_items as $selected_item) {
            $selected_item = is_array($selected_item) ? $selected_item : array();
            if (empty($selected_item['accepted_by_user'])) {
                $gate_reasons[] = 'accepted_internal_linking_rows_required';
                break;
            }
            if (sanitize_text_field((string) ($selected_item['approval_token'] ?? '')) === '') {
                $gate_reasons[] = 'internal_linking_approval_token_required';
                break;
            }
            if (sanitize_text_field((string) ($selected_item['before_content_hash'] ?? '')) === '' || sanitize_text_field((string) ($selected_item['after_content_hash'] ?? '')) === '') {
                $gate_reasons[] = 'internal_linking_content_hash_required';
                break;
            }
            if (sanitize_text_field((string) ($selected_item['insertion_location_hash'] ?? '')) === '') {
                $gate_reasons[] = 'internal_linking_location_hash_required';
                break;
            }
            if (!in_array(sanitize_key((string) ($selected_item['internal_link_classification'] ?? '')), array('safe_insert', 'safe_update_anchor'), true)) {
                $gate_reasons[] = 'internal_linking_unsafe_classification';
                break;
            }
        }
        if (!empty($payload['dry_run']) || empty($payload['writes_enabled'])) {
            $gate_reasons[] = 'writes_not_requested';
        }
        if ($gate_reasons !== array()) {
            $result['status'] = $selected_items === array() ? 'no_executable_fixes' : 'blocked';
            $result['reason'] = $gate_reasons[0];
            $result['gate_reasons'] = array_values(array_unique($gate_reasons));
            if ($skipped_rejected !== array()) {
                $result['item_results'] = $skipped_rejected;
                $result['skipped_count'] = count($skipped_rejected);
            }
            return $result;
        }

        $unit_cost_cents = max(1, intval($payload['billing_unit_cents'] ?? 5));
        $max_cost_cents = max(count($selected_items) * $unit_cost_cents, intval($payload['max_cost_cents'] ?? 0));
        $billing_preflight = $this->aysa_agent8v3_image_alt_test_billing_preflight($max_cost_cents, $idempotency_key, 'internal_linking_agent8_flow');
        $result['billing_preflight'] = $billing_preflight;
        if (empty($billing_preflight['ok'])) {
            $result['status'] = 'blocked';
            $result['reason'] = 'internal_linking_billing_preflight_failed';
            $result['gate_reasons'] = array('internal_linking_billing_preflight_failed');
            $result['billing_status'] = 'billing_preflight_failed';
            return $result;
        }

        $items_hash = hash('sha256', wp_json_encode($selected_items));
        $records = get_option('aysa_agent8v3_internal_linking_apply_idempotency', array());
        $records = is_array($records) ? $records : array();
        if (isset($records[$idempotency_key]) && is_array($records[$idempotency_key])) {
            $record = $records[$idempotency_key];
            if (sanitize_text_field((string) ($record['items_hash'] ?? '')) !== $items_hash) {
                $result['status'] = 'idempotency_conflict';
                $result['reason'] = 'idempotency_key_payload_mismatch';
                return $result;
            }
            $replay = is_array($record['result'] ?? null) ? $record['result'] : array();
            $replay['idempotent_replay'] = true;
            return array_merge($result, $replay);
        }

        $context = array(
            'project_id' => intval($project_id),
            'site_host' => $this->aysa_agent8v3_site_host(),
            'idempotency_key' => $idempotency_key,
            'payload_hash' => $payload_hash,
            'source_run_id' => intval($payload['parent_technical_run_id'] ?? $payload['run_id'] ?? 0),
        );
        foreach ($selected_items as $item) {
            $item_result = $this->aysa_agent8v3_apply_single_internal_linking_item($item, $context);
            $result['item_results'][] = $item_result;
            $status = sanitize_key((string) ($item_result['status'] ?? 'failed'));
            if ($status === 'verified') {
                $result['verified_count']++;
                $result['applied_count']++;
            } elseif (str_starts_with($status, 'skipped')) {
                $result['skipped_count']++;
            } elseif ($status === 'manual_review_needed') {
                $result['manual_needed_count']++;
            } else {
                $result['failed_count']++;
            }
        }
        foreach ($skipped_rejected as $item_result) {
            $result['item_results'][] = $item_result;
            $result['skipped_count']++;
        }
        if ($result['verified_count'] > 0 && $result['failed_count'] === 0 && $result['manual_needed_count'] === 0) {
            $result['status'] = 'verified';
        } elseif ($result['verified_count'] > 0) {
            $result['status'] = 'partial_failed';
        } elseif ($result['skipped_count'] > 0 && $result['failed_count'] === 0 && $result['manual_needed_count'] === 0) {
            $result['status'] = 'skipped';
        } elseif ($result['manual_needed_count'] > 0 && $result['failed_count'] === 0) {
            $result['status'] = 'manual_review_needed';
        } else {
            $result['status'] = 'failed';
        }
        $result['writes_enabled'] = true;
        $result['completed_at'] = current_time('mysql');
        $billing_result = $this->aysa_agent8v3_bill_internal_linking_apply_result($project_id, $payload, $result);
        $this->aysa_agent8v3_update_internal_linking_history_billing(is_array($result['item_results'] ?? null) ? $result['item_results'] : array(), $billing_result);
        $result['billing'] = $billing_result;
        $result['billing_status'] = sanitize_key((string) ($billing_result['status'] ?? 'billing_not_applicable'));
        $result['billing_cost_cents'] = intval($billing_result['cost_cents'] ?? 0);
        $result['actual_credits_cents'] = intval($billing_result['cost_cents'] ?? 0);
        $result['billing_external_recorded'] = in_array($result['billing_status'], array('billed', 'idempotent_replay', 'not_billable_no_verified_changes'), true);
        $result['billing_reference'] = sanitize_text_field((string) ($billing_result['reference'] ?? ''));

        $records[$idempotency_key] = array('items_hash' => $items_hash, 'result' => $result, 'timestamp' => current_time('mysql'));
        update_option('aysa_agent8v3_internal_linking_apply_idempotency', array_slice($records, -100, null, true), false);
        return $result;
    }

    private function aysa_agent8v3_apply_single_internal_linking_item($item, $context = array()) {
        $item = is_array($item) ? $item : array();
        $context = is_array($context) ? $context : array();
        $source_post_id = intval($item['source_post_id'] ?? 0);
        $target_post_id = intval($item['target_post_id'] ?? 0);
        $source_url = esc_url_raw((string) ($item['source_url'] ?? ''));
        $target_url = esc_url_raw((string) ($item['target_url'] ?? ''));
        $anchor = sanitize_text_field((string) ($item['anchor_text'] ?? $item['proposed_anchor_text'] ?? ''));
        $current_snippet = (string) ($item['current_html_snippet'] ?? '');
        $proposed_snippet = (string) ($item['proposed_html_snippet'] ?? '');
        $classification = sanitize_key((string) ($item['internal_link_classification'] ?? ''));
        $before_content_hash = sanitize_text_field((string) ($item['before_content_hash'] ?? ''));
        $after_content_hash = sanitize_text_field((string) ($item['after_content_hash'] ?? ''));
        $insertion_location_hash = sanitize_text_field((string) ($item['insertion_location_hash'] ?? ''));
        $approval_token = sanitize_text_field((string) ($item['approval_token'] ?? ''));
        $payload_hash = sanitize_text_field((string) ($item['payload_hash'] ?? $item['row_hash'] ?? ''));
        $result = array(
            'item_id' => sanitize_text_field((string) ($item['item_id'] ?? $item['row_id'] ?? $source_post_id . '_' . $target_post_id)),
            'source_post_id' => $source_post_id,
            'target_post_id' => $target_post_id,
            'source_url' => $source_url,
            'target_url' => $target_url,
            'anchor_text' => $anchor,
            'changed' => false,
            'verification_status' => 'not_verified',
        );
        if (empty($item['accepted_by_user'])) {
            return array_merge($result, array('status' => 'skipped_not_accepted', 'skipped_reason' => 'item_not_accepted'));
        }
        if (!in_array($classification, array('safe_insert', 'safe_update_anchor'), true)) {
            return array_merge($result, array('status' => 'manual_review_needed', 'skipped_reason' => 'internal_linking_unsafe_classification'));
        }
        if ($approval_token === '' || $before_content_hash === '' || $after_content_hash === '' || $insertion_location_hash === '' || $payload_hash === '') {
            return array_merge($result, array('status' => 'manual_review_needed', 'skipped_reason' => 'internal_linking_preview_evidence_missing'));
        }
        if ($source_post_id <= 0 || $target_post_id <= 0 || !get_post($source_post_id) || !get_post($target_post_id)) {
            return array_merge($result, array('status' => 'skipped_missing_item', 'skipped_reason' => 'source_or_target_missing'));
        }
        if (!in_array(get_post_status($source_post_id), array('publish'), true) || !in_array(get_post_status($target_post_id), array('publish'), true)) {
            return array_merge($result, array('status' => 'manual_review_needed', 'skipped_reason' => 'source_or_target_not_published'));
        }
        if (!in_array(get_post_type($source_post_id), array('post', 'page'), true) || !in_array(get_post_type($target_post_id), array('post', 'page'), true)) {
            return array_merge($result, array('status' => 'manual_review_needed', 'skipped_reason' => 'unsupported_post_type'));
        }
        if (intval(get_option('page_on_front')) === $source_post_id || $this->aysa_agent8v3_internal_linking_critical_url($source_url)) {
            return array_merge($result, array('status' => 'manual_review_needed', 'skipped_reason' => 'critical_source_page_blocked'));
        }
        if (!$this->aysa_agent8v3_internal_linking_same_site_url($source_url) || !$this->aysa_agent8v3_internal_linking_same_site_url($target_url)) {
            return array_merge($result, array('status' => 'manual_review_needed', 'skipped_reason' => 'source_or_target_not_internal'));
        }
        if ($this->aysa_agent8v3_post_has_noindex_signal($target_post_id) || !$this->aysa_agent8v3_internal_linking_target_self_canonical($target_post_id, $target_url)) {
            return array_merge($result, array('status' => 'manual_review_needed', 'skipped_reason' => 'target_not_indexable'));
        }
        $target_status = $this->aysa_agent8v3_internal_linking_url_status($target_url);
        if ($target_status !== 200) {
            return array_merge($result, array('status' => 'manual_review_needed', 'skipped_reason' => 'target_not_200', 'target_status' => $target_status));
        }
        if ((int) ($item['final_relevance_score'] ?? 0) < 80) {
            return array_merge($result, array('status' => 'manual_review_needed', 'skipped_reason' => 'weak_relevance'));
        }
        if (trim((string) ($item['keyword_relevance_reason'] ?? $item['keyword_match_reason'] ?? '')) === '') {
            return array_merge($result, array('status' => 'manual_review_needed', 'skipped_reason' => 'missing_keyword_mapping'));
        }
        if ($this->aysa_agent8v3_internal_linking_generic_anchor($anchor)) {
            return array_merge($result, array('status' => 'manual_review_needed', 'skipped_reason' => 'generic_anchor'));
        }

        $content = (string) get_post_field('post_content', $source_post_id);
        if (function_exists('has_blocks') && has_blocks($content)) {
            return array_merge($result, array('status' => 'manual_review_needed', 'skipped_reason' => 'unsupported_editor'));
        }
        if ($this->aysa_agent8v3_internal_linking_content_has_target($content, $target_url)) {
            return array_merge($result, array('status' => 'skipped_no_change', 'verification_status' => 'verified_no_change', 'skipped_reason' => 'duplicate_link_exists'));
        }
        $density = $this->aysa_agent8v3_internal_linking_density_snapshot($source_post_id, $content, 1);
        if (!empty($density['overlinking_risk']) || (string) ($density['density_status'] ?? '') === 'over_limit') {
            return array_merge($result, array(
                'status' => 'manual_review_needed',
                'skipped_reason' => 'overlinking_risk',
                'density' => $density,
            ));
        }
        if ((int) ($density['projected_contextual_internal_links_count'] ?? 0) >= 4 && (int) ($item['final_relevance_score'] ?? 0) < 90) {
            return array_merge($result, array(
                'status' => 'manual_review_needed',
                'skipped_reason' => 'density_manual_review_required',
                'density' => $density,
            ));
        }
        if ((int) ($item['projected_contextual_internal_links_count'] ?? $density['projected_contextual_internal_links_count'] ?? 0) !== (int) ($density['projected_contextual_internal_links_count'] ?? 0)) {
            return array_merge($result, array(
                'status' => 'manual_review_needed',
                'skipped_reason' => 'density_changed_since_preview',
                'density' => $density,
            ));
        }
        if ($current_snippet === '' || $proposed_snippet === '' || strpos($proposed_snippet, '<a ') === false || strpos($proposed_snippet, $target_url) === false) {
            return array_merge($result, array('status' => 'manual_review_needed', 'skipped_reason' => 'exact_diff_required'));
        }
        $expected_location_hash = hash('sha256', $source_url . '|' . $target_url . '|' . $anchor . '|' . $current_snippet);
        if (!hash_equals($expected_location_hash, $insertion_location_hash)) {
            return array_merge($result, array('status' => 'manual_review_needed', 'skipped_reason' => 'insertion_location_hash_mismatch'));
        }
        $expected_approval_token = substr(hash('sha256', implode('|', array(
            (string) $source_post_id,
            (string) $target_post_id,
            $source_url,
            $target_url,
            $anchor,
            $current_snippet,
            $proposed_snippet,
            $payload_hash,
        ))), 0, 32);
        if (!hash_equals($expected_approval_token, $approval_token)) {
            return array_merge($result, array('status' => 'manual_review_needed', 'skipped_reason' => 'approval_token_mismatch'));
        }
        $current_snippet_hash = hash('sha256', $current_snippet);
        $current_content_hash = hash('sha256', $content);
        if (!hash_equals($before_content_hash, $current_snippet_hash) && !hash_equals($before_content_hash, $current_content_hash)) {
            return array_merge($result, array('status' => 'manual_review_needed', 'skipped_reason' => 'before_content_hash_mismatch'));
        }
        if (!hash_equals($after_content_hash, hash('sha256', $proposed_snippet))) {
            return array_merge($result, array('status' => 'manual_review_needed', 'skipped_reason' => 'after_content_hash_mismatch'));
        }
        if (substr_count($content, $current_snippet) !== 1) {
            return array_merge($result, array('status' => 'manual_review_needed', 'skipped_reason' => 'stale_current_content'));
        }

        $new_content = $this->aysa_agent8v3_replace_first($content, $current_snippet, $proposed_snippet);
        if ($new_content === $content || $this->aysa_agent8v3_internal_linking_content_delta_too_large($content, $new_content, $current_snippet, $proposed_snippet)) {
            return array_merge($result, array('status' => 'failed', 'error_code' => 'unexpected_content_diff'));
        }
        if (!hash_equals($after_content_hash, hash('sha256', $proposed_snippet)) && !hash_equals($after_content_hash, hash('sha256', $new_content))) {
            return array_merge($result, array('status' => 'manual_review_needed', 'skipped_reason' => 'after_content_hash_mismatch'));
        }

        $history_id = $this->aysa_agent8v3_create_internal_linking_history_row($item, array_merge($context, array('density' => $density)), $content, $new_content);
        $updated = wp_update_post(array('ID' => $source_post_id, 'post_content' => wp_slash($new_content)), true);
        if (is_wp_error($updated)) {
            $this->aysa_agent8v3_update_internal_linking_history_row($history_id, array('status' => 'failed', 'verification_status' => 'failed', 'error_code' => 'wp_update_post_failed'));
            return array_merge($result, array('status' => 'failed', 'history_id' => $history_id, 'error_code' => 'wp_update_post_failed'));
        }

        $verified_content = (string) get_post_field('post_content', $source_post_id);
        $verified = $this->aysa_agent8v3_internal_linking_content_has_target($verified_content, $target_url)
            && strpos($verified_content, $proposed_snippet) !== false
            && hash('sha256', $verified_content) === hash('sha256', $new_content);
        $this->aysa_agent8v3_update_internal_linking_history_row($history_id, array(
            'status' => $verified ? 'changed' : 'failed',
            'verification_status' => $verified ? 'verified' : 'failed',
            'error_code' => $verified ? '' : 'readback_verification_failed',
        ));
        return array_merge($result, array(
            'status' => $verified ? 'verified' : 'failed',
            'changed' => $verified,
            'verification_status' => $verified ? 'verified' : 'failed',
            'history_id' => $history_id,
            'old_content_hash' => hash('sha256', $content),
            'new_content_hash' => hash('sha256', $new_content),
            'error_code' => $verified ? '' : 'readback_verification_failed',
        ));
    }

    private function aysa_agent8v3_internal_linking_density_snapshot($post_id, $content, $proposed_new_links = 0) {
        $content = (string) $content;
        $site_host = $this->aysa_agent8v3_site_host();
        preg_match_all('/<a\s+[^>]*href=["\']([^"\']+)["\'][^>]*>/i', $content, $matches, PREG_SET_ORDER);
        $internal_links = array();
        $aysa_links = 0;
        foreach ($matches as $match) {
            $href = esc_url_raw((string) ($match[1] ?? ''));
            if ($href === '' || str_starts_with($href, '#') || str_starts_with($href, 'mailto:') || str_starts_with($href, 'tel:')) {
                continue;
            }
            if (str_starts_with($href, '/')) {
                $href = home_url($href);
            }
            $host = strtolower((string) wp_parse_url($href, PHP_URL_HOST));
            $host = preg_replace('/^www\./', '', $host);
            if ($host === '' || $host !== $site_host) {
                continue;
            }
            $internal_links[] = $this->aysa_agent8v3_normalize_url_for_canonical($href);
            if (stripos((string) ($match[0] ?? ''), 'data-aysa-internal-link') !== false || stripos((string) ($match[0] ?? ''), 'aysa') !== false) {
                $aysa_links++;
            }
        }
        $existing = count(array_values(array_unique(array_filter($internal_links))));
        $proposed = max(0, intval($proposed_new_links));
        $projected = $existing + $proposed;
        $status = 'ok';
        if ($projected > 4) {
            $status = 'over_limit';
        } elseif ($projected === 4) {
            $status = 'at_limit';
        } elseif ($projected === 3) {
            $status = 'near_limit';
        }
        return array(
            'source_post_id' => intval($post_id),
            'existing_contextual_internal_links_count' => $existing,
            'existing_aysa_internal_links_count' => $aysa_links,
            'proposed_new_links_count' => $proposed,
            'projected_contextual_internal_links_count' => $projected,
            'max_contextual_internal_links_allowed' => 4,
            'recommended_contextual_internal_links_max' => 3,
            'overlinking_risk' => $projected > 4,
            'density_status' => $status,
        );
    }

    private function aysa_agent8v3_internal_linking_history_rows() {
        $rows = get_option('aysa_agent8v3_internal_linking_apply_history', array());
        return is_array($rows) ? array_values(array_filter($rows, 'is_array')) : array();
    }

    private function aysa_agent8v3_save_internal_linking_history_rows($rows) {
        $rows = is_array($rows) ? array_values(array_filter($rows, 'is_array')) : array();
        update_option('aysa_agent8v3_internal_linking_apply_history', array_slice($rows, -200), false);
    }

    private function aysa_agent8v3_create_internal_linking_history_row($item, $context, $old_content, $new_content) {
        $item = is_array($item) ? $item : array();
        $context = is_array($context) ? $context : array();
        $density = is_array($context['density'] ?? null) ? $context['density'] : $this->aysa_agent8v3_internal_linking_density_snapshot(intval($item['source_post_id'] ?? 0), (string) $old_content, 1);
        $history_id = 'ilh_' . time() . '_' . wp_generate_password(8, false, false);
        $row = array(
            'source' => 'agent8v3_internal_linking_apply',
            'history_id' => $history_id,
            'project_id' => intval($context['project_id'] ?? 0),
            'site_host' => sanitize_text_field((string) ($context['site_host'] ?? $this->aysa_agent8v3_site_host())),
            'run_id' => intval($context['source_run_id'] ?? 0),
            'task_id' => intval($context['task_id'] ?? 0),
            'package_id' => sanitize_text_field((string) ($item['package_id'] ?? 'internal_linking_discovery')),
            'row_id' => sanitize_text_field((string) ($item['row_id'] ?? $item['item_id'] ?? '')),
            'source_post_id' => intval($item['source_post_id'] ?? 0),
            'target_post_id' => intval($item['target_post_id'] ?? 0),
            'source_url' => esc_url_raw((string) ($item['source_url'] ?? '')),
            'target_url' => esc_url_raw((string) ($item['target_url'] ?? '')),
            'source_primary_keyword' => sanitize_text_field((string) ($item['source_primary_keyword'] ?? '')),
            'source_topic_cluster' => sanitize_text_field((string) ($item['source_topic_cluster'] ?? '')),
            'target_primary_keyword' => sanitize_text_field((string) ($item['target_primary_keyword'] ?? '')),
            'target_topic_cluster' => sanitize_text_field((string) ($item['target_topic_cluster'] ?? '')),
            'anchor_text' => sanitize_text_field((string) ($item['anchor_text'] ?? $item['proposed_anchor_text'] ?? '')),
            'anchor_source' => sanitize_text_field((string) ($item['anchor_source'] ?? '')),
            'keyword_relevance_reason' => sanitize_text_field((string) ($item['keyword_relevance_reason'] ?? $item['keyword_match_reason'] ?? '')),
            'final_relevance_score' => intval($item['final_relevance_score'] ?? 0),
            'current_html_snippet_before' => sanitize_text_field((string) ($item['current_html_snippet'] ?? '')),
            'proposed_html_snippet' => sanitize_text_field((string) ($item['proposed_html_snippet'] ?? '')),
            'applied_html_snippet' => sanitize_text_field((string) ($item['proposed_html_snippet'] ?? '')),
            'old_content_hash' => hash('sha256', (string) $old_content),
            'new_content_hash' => hash('sha256', (string) $new_content),
            'old_content_b64' => base64_encode((string) $old_content),
            'new_content_b64' => base64_encode((string) $new_content),
            'diff_summary' => sanitize_text_field((string) ($item['diff_summary'] ?? '')),
            'source_run_id' => intval($context['source_run_id'] ?? 0),
            'payload_hash' => sanitize_text_field((string) ($context['payload_hash'] ?? '')),
            'idempotency_key' => sanitize_text_field((string) ($context['idempotency_key'] ?? '')),
            'timestamp' => current_time('mysql'),
            'updated_at' => current_time('mysql'),
            'billing_action_key' => 'agent8v3_internal_linking_apply',
            'billing_status' => 'pending_result',
            'billing_cost_cents' => 0,
            'verification_status' => 'pending',
            'status' => 'submitted',
            'restore_available' => true,
            'restored' => false,
            'restored_at' => '',
            'restore_reference' => '',
            'source_existing_contextual_internal_links_count' => intval($density['existing_contextual_internal_links_count'] ?? 0),
            'source_existing_aysa_internal_links_count' => intval($density['existing_aysa_internal_links_count'] ?? 0),
            'source_projected_contextual_internal_links_count' => intval($density['projected_contextual_internal_links_count'] ?? 0),
            'max_contextual_internal_links_allowed' => intval($density['max_contextual_internal_links_allowed'] ?? 4),
            'recommended_contextual_internal_links_max' => intval($density['recommended_contextual_internal_links_max'] ?? 3),
            'density_status_at_apply' => sanitize_key((string) ($density['density_status'] ?? 'unknown')),
            'error_code' => '',
        );
        $rows = $this->aysa_agent8v3_internal_linking_history_rows();
        $rows[] = $row;
        $this->aysa_agent8v3_save_internal_linking_history_rows($rows);
        return $history_id;
    }

    private function aysa_agent8v3_update_internal_linking_history_row($history_id, $updates) {
        $history_id = sanitize_text_field((string) $history_id);
        if ($history_id === '') {
            return;
        }
        $rows = $this->aysa_agent8v3_internal_linking_history_rows();
        foreach ($rows as $index => $row) {
            if (sanitize_text_field((string) ($row['history_id'] ?? '')) !== $history_id) {
                continue;
            }
            foreach ((array) $updates as $key => $value) {
                $safe_key = sanitize_key((string) $key);
                if ($safe_key === '') {
                    continue;
                }
                if (in_array($safe_key, array('billing_cost_cents', 'source_post_id', 'target_post_id', 'project_id', 'source_run_id'), true)) {
                    $row[$safe_key] = intval($value);
                } elseif (in_array($safe_key, array('source_url', 'target_url'), true)) {
                    $row[$safe_key] = esc_url_raw((string) $value);
                } else {
                    $row[$safe_key] = sanitize_text_field((string) $value);
                }
            }
            $row['updated_at'] = current_time('mysql');
            $rows[$index] = $row;
            $this->aysa_agent8v3_save_internal_linking_history_rows($rows);
            return;
        }
    }

    private function aysa_agent8v3_update_internal_linking_history_billing($item_results, $billing_result) {
        $billing_result = is_array($billing_result) ? $billing_result : array();
        $unit_cost = intval($billing_result['unit_cost_cents'] ?? 5);
        $billing_status = sanitize_key((string) ($billing_result['status'] ?? 'not_billed'));
        foreach ((array) $item_results as $item_result) {
            if (!is_array($item_result) || empty($item_result['history_id'])) {
                continue;
            }
            $cost = (sanitize_key((string) ($item_result['status'] ?? '')) === 'verified' && !empty($item_result['changed']) && in_array($billing_status, array('billed', 'idempotent_replay'), true)) ? $unit_cost : 0;
            $this->aysa_agent8v3_update_internal_linking_history_row($item_result['history_id'], array(
                'billing_status' => $cost > 0 ? $billing_status : 'not_billable',
                'billing_cost_cents' => $cost,
                'billing_reference' => sanitize_text_field((string) ($billing_result['reference'] ?? '')),
            ));
        }
    }

    private function aysa_agent8v3_internal_linking_history_projection_rows() {
        $rows = array();
        foreach ($this->aysa_agent8v3_internal_linking_history_rows() as $row) {
            $post_id = intval($row['source_post_id'] ?? 0);
            $target_id = intval($row['target_post_id'] ?? 0);
            $current_content = $post_id > 0 ? (string) get_post_field('post_content', $post_id) : '';
            $target_url = esc_url_raw((string) ($row['target_url'] ?? ''));
            $anchor = sanitize_text_field((string) ($row['anchor_text'] ?? ''));
            $density = $this->aysa_agent8v3_internal_linking_density_snapshot($post_id, $current_content, 0);
            $current_status = $this->aysa_agent8v3_internal_linking_history_current_status($row, $current_content);
            $rows[] = array(
                'history_id' => sanitize_text_field((string) ($row['history_id'] ?? '')),
                'project_id' => intval($row['project_id'] ?? 0),
                'site_host' => sanitize_text_field((string) ($row['site_host'] ?? '')),
                'run_id' => intval($row['source_run_id'] ?? $row['run_id'] ?? 0),
                'task_id' => intval($row['task_id'] ?? 0),
                'package_id' => sanitize_text_field((string) ($row['package_id'] ?? 'internal_linking_discovery')),
                'row_id' => sanitize_text_field((string) ($row['row_id'] ?? '')),
                'source_post_id' => $post_id,
                'source_url' => esc_url_raw((string) ($row['source_url'] ?? '')),
                'source_title' => $post_id > 0 ? get_the_title($post_id) : '',
                'source_primary_keyword' => sanitize_text_field((string) ($row['source_primary_keyword'] ?? '')),
                'source_topic_cluster' => sanitize_text_field((string) ($row['source_topic_cluster'] ?? '')),
                'target_post_id' => $target_id,
                'target_url' => $target_url,
                'target_title' => $target_id > 0 ? get_the_title($target_id) : '',
                'target_primary_keyword' => sanitize_text_field((string) ($row['target_primary_keyword'] ?? '')),
                'target_topic_cluster' => sanitize_text_field((string) ($row['target_topic_cluster'] ?? '')),
                'anchor_text' => $anchor,
                'anchor_source' => sanitize_text_field((string) ($row['anchor_source'] ?? '')),
                'keyword_relevance_reason' => sanitize_text_field((string) ($row['keyword_relevance_reason'] ?? '')),
                'final_relevance_score' => intval($row['final_relevance_score'] ?? 0),
                'diff_summary' => sanitize_text_field((string) ($row['diff_summary'] ?? '')),
                'old_content_hash' => sanitize_text_field((string) ($row['old_content_hash'] ?? '')),
                'new_content_hash' => sanitize_text_field((string) ($row['new_content_hash'] ?? '')),
                'inserted_at' => sanitize_text_field((string) ($row['timestamp'] ?? '')),
                'inserted_by_agent' => sanitize_text_field((string) ($row['source'] ?? 'agent8v3_internal_linking_apply')),
                'billing_action_key' => sanitize_text_field((string) ($row['billing_action_key'] ?? 'agent8v3_internal_linking_apply')),
                'billed_amount' => intval($row['billing_cost_cents'] ?? 0),
                'billing_reference' => sanitize_text_field((string) ($row['billing_reference'] ?? '')),
                'verification_status' => sanitize_key((string) ($row['verification_status'] ?? '')),
                'last_verified_at' => sanitize_text_field((string) ($row['last_verified_at'] ?? $row['updated_at'] ?? '')),
                'current_status' => $current_status,
                'restore_available' => $this->aysa_agent8v3_internal_linking_history_restore_available($row, $current_content),
                'restored_at' => sanitize_text_field((string) ($row['restored_at'] ?? '')),
                'restore_reference' => sanitize_text_field((string) ($row['restore_reference'] ?? '')),
                'idempotency_key' => sanitize_text_field((string) ($row['idempotency_key'] ?? '')),
                'approval_token' => sanitize_text_field((string) ($row['approval_token'] ?? '')),
                'insertion_location_hash' => sanitize_text_field((string) ($row['insertion_location_hash'] ?? '')),
                'preview_before_content_hash' => sanitize_text_field((string) ($row['preview_before_content_hash'] ?? '')),
                'preview_after_content_hash' => sanitize_text_field((string) ($row['preview_after_content_hash'] ?? '')),
                'internal_link_classification' => sanitize_key((string) ($row['internal_link_classification'] ?? '')),
                'source_existing_contextual_internal_links_count' => intval($row['source_existing_contextual_internal_links_count'] ?? $row['existing_contextual_internal_links_count'] ?? 0),
                'source_existing_aysa_internal_links_count' => intval($row['source_existing_aysa_internal_links_count'] ?? $row['existing_aysa_internal_links_count'] ?? 0),
                'source_projected_contextual_internal_links_count' => intval($row['source_projected_contextual_internal_links_count'] ?? $row['projected_contextual_internal_links_count'] ?? 0),
                'current_contextual_internal_links_count' => intval($density['existing_contextual_internal_links_count'] ?? 0),
                'current_aysa_internal_links_count' => intval($density['existing_aysa_internal_links_count'] ?? 0),
                'max_contextual_internal_links_allowed' => intval($density['max_contextual_internal_links_allowed'] ?? 4),
                'recommended_contextual_internal_links_max' => intval($density['recommended_contextual_internal_links_max'] ?? 3),
                'density_status_at_apply' => sanitize_key((string) ($row['density_status_at_apply'] ?? 'unknown')),
                'current_density_status' => sanitize_key((string) ($density['density_status'] ?? 'unknown')),
                'actions' => array(
                    'reverify' => array(
                        'operation' => 'reverify',
                        'label' => 'Re-verify',
                        'history_id' => sanitize_text_field((string) ($row['history_id'] ?? '')),
                        'billing_status' => 'not_billable_reverify',
                    ),
                    'restore' => array(
                        'operation' => 'restore',
                        'label' => 'Restore / Remove AYSA link',
                        'history_id' => sanitize_text_field((string) ($row['history_id'] ?? '')),
                        'disabled' => !$this->aysa_agent8v3_internal_linking_history_restore_available($row, $current_content),
                        'billing_status' => 'not_billable_restore',
                    ),
                ),
            );
        }
        return array_reverse($rows);
    }

    private function aysa_agent8v3_internal_linking_history_current_status($row, $current_content) {
        $row = is_array($row) ? $row : array();
        if (!empty($row['restored'])) {
            return 'restored';
        }
        $post_id = intval($row['source_post_id'] ?? 0);
        $target_url = esc_url_raw((string) ($row['target_url'] ?? ''));
        $anchor = sanitize_text_field((string) ($row['anchor_text'] ?? ''));
        if ($post_id <= 0 || !get_post($post_id)) {
            return 'source_not_available';
        }
        if (intval($row['target_post_id'] ?? 0) > 0 && !get_post(intval($row['target_post_id'] ?? 0))) {
            return 'target_not_available';
        }
        if ($target_url === '' || !$this->aysa_agent8v3_internal_linking_content_has_target((string) $current_content, $target_url)) {
            return 'missing';
        }
        if ($anchor !== '' && strpos(wp_strip_all_tags((string) $current_content), $anchor) === false) {
            return 'manual_review_needed';
        }
        $new_hash = sanitize_text_field((string) ($row['new_content_hash'] ?? ''));
        if ($new_hash !== '' && hash('sha256', (string) $current_content) !== $new_hash) {
            return 'changed_outside_aysa';
        }
        return 'active';
    }

    private function aysa_agent8v3_internal_linking_history_restore_available($row, $current_content = null) {
        $row = is_array($row) ? $row : array();
        if (empty($row['restore_available']) || !empty($row['restored'])) {
            return false;
        }
        $post_id = intval($row['source_post_id'] ?? 0);
        if ($post_id <= 0 || !get_post($post_id)) {
            return false;
        }
        $old_content = base64_decode((string) ($row['old_content_b64'] ?? ''), true);
        if (!is_string($old_content) || $old_content === '') {
            return false;
        }
        $new_hash = sanitize_text_field((string) ($row['new_content_hash'] ?? ''));
        $content = $current_content === null ? (string) get_post_field('post_content', $post_id) : (string) $current_content;
        return $new_hash === '' || hash('sha256', $content) === $new_hash;
    }

    public function handle_agent8v3_internal_linking_history_action() {
        if (!$this->aysa_agent8v3_image_alt_test_authorized()) {
            wp_send_json_error(array('message' => 'Unauthorized'), 403);
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $operation = sanitize_key((string) wp_unslash($_POST['operation'] ?? ''));
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $history_id = sanitize_text_field((string) wp_unslash($_POST['history_id'] ?? ''));
        $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
        if ($operation === 'reverify') {
            wp_send_json_success($this->aysa_agent8v3_internal_linking_reverify_history_row($history_id));
        }
        if ($operation === 'restore') {
            wp_send_json_success($this->aysa_agent8v3_internal_linking_restore_bridge($project_id, array('history_id' => $history_id)));
        }
        wp_send_json_error(array('message' => 'Unsupported operation'), 400);
    }

    private function aysa_agent8v3_internal_linking_reverify_history_row($history_id) {
        $history_id = sanitize_text_field((string) $history_id);
        $result = array(
            'operation' => 'internal_linking_reverify',
            'module' => 'internal_linking',
            'history_id' => $history_id,
            'billing_status' => 'not_billable_reverify',
            'billing_cost_cents' => 0,
            'actual_credits_cents' => 0,
            'writes_enabled' => false,
        );
        if ($history_id === '') {
            return array_merge($result, array('status' => 'failed', 'reason' => 'history_id_required'));
        }
        $rows = $this->aysa_agent8v3_internal_linking_history_rows();
        foreach ($rows as $row) {
            if (sanitize_text_field((string) ($row['history_id'] ?? '')) !== $history_id) {
                continue;
            }
            $post_id = intval($row['source_post_id'] ?? 0);
            $target_id = intval($row['target_post_id'] ?? 0);
            $target_url = esc_url_raw((string) ($row['target_url'] ?? ''));
            $content = $post_id > 0 ? (string) get_post_field('post_content', $post_id) : '';
            $status = $this->aysa_agent8v3_internal_linking_history_current_status($row, $content);
            $target_status = $target_url !== '' ? $this->aysa_agent8v3_internal_linking_url_status($target_url) : 0;
            if ($post_id <= 0 || !get_post($post_id)) {
                $status = 'source_not_available';
            } elseif ($target_id > 0 && !get_post($target_id)) {
                $status = 'target_not_available';
            } elseif ($target_status > 0 && $target_status !== 200) {
                $status = 'target_not_available';
            }
            $density = $this->aysa_agent8v3_internal_linking_density_snapshot($post_id, $content, 0);
            if ($status === 'active' && in_array((string) ($density['density_status'] ?? ''), array('at_limit', 'over_limit'), true)) {
                $status = 'overlinking_risk';
            }
            $this->aysa_agent8v3_update_internal_linking_history_row($history_id, array(
                'current_status' => $status,
                'verification_status' => in_array($status, array('active', 'overlinking_risk'), true) ? 'verified' : $status,
                'last_verified_at' => current_time('mysql'),
                'current_contextual_internal_links_count' => intval($density['existing_contextual_internal_links_count'] ?? 0),
                'current_aysa_internal_links_count' => intval($density['existing_aysa_internal_links_count'] ?? 0),
                'current_density_status' => sanitize_key((string) ($density['density_status'] ?? 'unknown')),
            ));
            return array_merge($result, array(
                'status' => $status,
                'verification_status' => in_array($status, array('active', 'overlinking_risk'), true) ? 'verified' : $status,
                'source_status' => $post_id > 0 && get_post($post_id) ? 200 : 404,
                'target_status' => $target_status,
                'density' => $density,
                'completed_at' => current_time('mysql'),
            ));
        }
        return array_merge($result, array('status' => 'failed', 'reason' => 'history_not_found'));
    }

    private function aysa_agent8v3_bill_internal_linking_apply_result($project_id, $payload, $result) {
        $idempotency_key = sanitize_text_field((string) ($payload['idempotency_key'] ?? ''));
        $verified_changed = 0;
        $source_post_ids = array();
        foreach ((array) ($result['item_results'] ?? array()) as $item) {
            if (is_array($item) && (string) ($item['status'] ?? '') === 'verified' && !empty($item['changed'])) {
                $verified_changed++;
                $source_post_ids[] = intval($item['source_post_id'] ?? 0);
            }
        }
        $unit_cost_cents = max(1, intval($payload['billing_unit_cents'] ?? 5));
        $cost_cents = $verified_changed * $unit_cost_cents;
        $base = array(
            'status' => $cost_cents > 0 ? 'pending' : 'not_billable_no_verified_changes',
            'unit_cost_cents' => $unit_cost_cents,
            'verified_changed_count' => $verified_changed,
            'cost_cents' => $cost_cents,
            'idempotency_key' => $idempotency_key,
        );
        if (intval($project_id) <= 0 || $idempotency_key === '' || $cost_cents <= 0) {
            return $base;
        }
        $records = get_option('aysa_agent8v3_internal_linking_apply_billing_idempotency', array());
        $records = is_array($records) ? $records : array();
        $billing_key = hash('sha256', 'internal_linking_apply_billing|' . intval($project_id) . '|' . $idempotency_key);
        if (isset($records[$billing_key]) && is_array($records[$billing_key])) {
            return array_merge($base, $records[$billing_key], array('status' => 'idempotent_replay'));
        }
        $wallet = $this->aysa_agent8v3_image_alt_test_wallet_manager();
        if (!$wallet || !method_exists($wallet, 'track_action_usage')) {
            return array_merge($base, array('status' => 'billing_failed', 'error' => 'wallet_unavailable'));
        }
        $reference = 'agent8v3_internal_linking_apply:' . substr($idempotency_key, -24);
        $ok = $wallet->track_action_usage(
            intval($project_id),
            'agent8v3_internal_linking_apply',
            $reference,
            array(
                'billing_policy' => 'verified_inserted_internal_links_only_cost_x5',
                'billing_context' => 'normal_agent8_flow',
                'billing_idempotency_key' => $billing_key,
                'payload_hash' => sanitize_text_field((string) ($payload['payload_hash'] ?? '')),
                'verified_changed_count' => $verified_changed,
                'unit_cost_cents' => $unit_cost_cents,
                'override_cost_cents' => $cost_cents,
                'source_post_ids' => array_values(array_filter(array_map('intval', $source_post_ids))),
            ),
            $cost_cents,
            'action:agent8v3_internal_linking_apply'
        );
        if (!$ok) {
            return array_merge($base, array('status' => 'billing_failed', 'error' => 'wallet_debit_failed'));
        }
        $record = array(
            'status' => 'billed',
            'cost_cents' => $cost_cents,
            'unit_cost_cents' => $unit_cost_cents,
            'verified_changed_count' => $verified_changed,
            'reference' => $reference,
            'action_key' => 'agent8v3_internal_linking_apply',
            'billed_at' => current_time('mysql'),
        );
        $records[$billing_key] = $record;
        update_option('aysa_agent8v3_internal_linking_apply_billing_idempotency', array_slice($records, -100, null, true), false);
        return array_merge($base, $record);
    }

    private function aysa_agent8v3_internal_linking_restore_bridge($project_id, $payload) {
        $history_id = sanitize_text_field((string) ($payload['history_id'] ?? ''));
        $result = array(
            'operation' => 'internal_linking_restore_bridge',
            'module' => 'internal_linking',
            'project_id' => intval($project_id),
            'history_id' => $history_id,
            'billing_status' => 'not_billable_restore',
            'billing_cost_cents' => 0,
            'actual_credits_cents' => 0,
            'writes_enabled' => false,
        );
        if ($history_id === '') {
            return array_merge($result, array('status' => 'failed', 'reason' => 'history_id_required'));
        }
        $rows = $this->aysa_agent8v3_internal_linking_history_rows();
        foreach ($rows as $row) {
            if (sanitize_text_field((string) ($row['history_id'] ?? '')) !== $history_id) {
                continue;
            }
            if (!empty($row['restored'])) {
                return array_merge($result, array('status' => 'verified', 'reason' => 'already_restored', 'writes_enabled' => false));
            }
            $post_id = intval($row['source_post_id'] ?? 0);
            $old_content = base64_decode((string) ($row['old_content_b64'] ?? ''), true);
            $new_hash = sanitize_text_field((string) ($row['new_content_hash'] ?? ''));
            $old_hash = sanitize_text_field((string) ($row['old_content_hash'] ?? ''));
            if ($post_id <= 0 || !get_post($post_id) || !is_string($old_content) || $old_content === '') {
                return array_merge($result, array('status' => 'failed', 'reason' => 'restore_target_missing'));
            }
            $current_content = (string) get_post_field('post_content', $post_id);
            if ($new_hash !== '' && hash('sha256', $current_content) !== $new_hash) {
                return array_merge($result, array('status' => 'manual_review_needed', 'reason' => 'current_content_changed_after_apply'));
            }
            $updated = wp_update_post(array('ID' => $post_id, 'post_content' => wp_slash($old_content)), true);
            if (is_wp_error($updated)) {
                return array_merge($result, array('status' => 'failed', 'reason' => 'wp_update_post_failed'));
            }
            $verified = $old_hash === '' || hash('sha256', (string) get_post_field('post_content', $post_id)) === $old_hash;
            $this->aysa_agent8v3_update_internal_linking_history_row($history_id, array(
                'restored' => $verified ? '1' : '0',
                'restored_at' => current_time('mysql'),
                'status' => $verified ? 'restored' : 'restore_failed',
                'current_status' => $verified ? 'restored' : 'restore_failed',
                'restore_status' => $verified ? 'verified' : 'failed',
                'restored_value' => $verified ? 'old_content_restored' : 'restore_verification_failed',
                'restore_reference' => 'internal_linking_restore:' . $history_id,
            ));
            return array_merge($result, array(
                'status' => $verified ? 'verified' : 'failed',
                'reason' => $verified ? '' : 'restore_verification_failed',
                'writes_enabled' => true,
                'source_post_id' => $post_id,
                'completed_at' => current_time('mysql'),
            ));
        }
        return array_merge($result, array('status' => 'failed', 'reason' => 'history_not_found'));
    }

    private function aysa_agent8v3_internal_linking_same_site_url($url) {
        $host = strtolower((string) wp_parse_url((string) $url, PHP_URL_HOST));
        $host = preg_replace('/^www\./', '', $host);
        return $host !== '' && $host === $this->aysa_agent8v3_site_host();
    }

    private function aysa_agent8v3_internal_linking_critical_url($url) {
        $path = strtolower((string) wp_parse_url((string) $url, PHP_URL_PATH));
        foreach (array('checkout', 'cart', 'booking', 'reservation', 'rezerv', 'payment', 'plata', 'pricing', 'pret') as $needle) {
            if (strpos($path, $needle) !== false) {
                return true;
            }
        }
        return false;
    }

    private function aysa_agent8v3_internal_linking_target_self_canonical($post_id, $target_url) {
        $target_url = esc_url_raw((string) $target_url);
        foreach (array('_yoast_wpseo_canonical', 'rank_math_canonical_url', 'aysa_canonical_url') as $key) {
            $canonical = esc_url_raw((string) get_post_meta(intval($post_id), $key, true));
            if ($canonical !== '' && $this->aysa_agent8v3_normalize_url_for_canonical($canonical) !== $this->aysa_agent8v3_normalize_url_for_canonical($target_url)) {
                return false;
            }
        }
        return true;
    }

    private function aysa_agent8v3_internal_linking_url_status($url) {
        $response = wp_remote_head(esc_url_raw((string) $url), array('timeout' => 5, 'redirection' => 0));
        if (is_wp_error($response)) {
            return 0;
        }
        return intval(wp_remote_retrieve_response_code($response));
    }

    private function aysa_agent8v3_internal_linking_generic_anchor($anchor) {
        $anchor = strtolower(trim((string) $anchor));
        return in_array($anchor, array('click here', 'here', 'read more', 'more', 'aici', 'click aici', 'vezi aici', 'detalii'), true);
    }

    private function aysa_agent8v3_internal_linking_content_has_target($content, $target_url) {
        $target_url = esc_url_raw((string) $target_url);
        return $target_url !== '' && preg_match('/<a\s+[^>]*href=["\']' . preg_quote($target_url, '/') . '["\'][^>]*>/i', (string) $content) === 1;
    }

    private function aysa_agent8v3_internal_linking_content_delta_too_large($old_content, $new_content, $old_snippet, $new_snippet) {
        return strlen((string) $new_content) - strlen((string) $old_content) !== strlen((string) $new_snippet) - strlen((string) $old_snippet);
    }

    private function aysa_agent8v3_replace_first($haystack, $needle, $replacement) {
        $pos = strpos((string) $haystack, (string) $needle);
        if ($pos === false) {
            return (string) $haystack;
        }
        return substr_replace((string) $haystack, (string) $replacement, $pos, strlen((string) $needle));
    }

    private function aysa_agent8v3_env_flag($name) {
        $name = strtoupper(preg_replace('/[^A-Z0-9_]/i', '', (string) $name));
        if ($name === '') {
            return false;
        }
        if (defined($name)) {
            return (bool) constant($name);
        }
        $env_value = getenv($name);
        return in_array(strtolower((string) $env_value), array('1', 'true', 'yes', 'on'), true);
    }

    private function aysa_agent8v3_qa_enabled() {
        if (!current_user_can('manage_options')) {
            return false;
        }
        if (defined('AYSA_AGENT8_V3_QA_ENABLED')) {
            return (bool) AYSA_AGENT8_V3_QA_ENABLED;
        }
        $env_value = getenv('AYSA_AGENT8_V3_QA_ENABLED');
        return in_array(strtolower((string) $env_value), array('1', 'true', 'yes', 'on'), true);
    }

    private function aysa_agent8v3_monitoring_flag($name) {
        $name = strtoupper(preg_replace('/[^A-Z0-9_]/i', '', (string) $name));
        if ($name === '') {
            return false;
        }
        if (defined($name)) {
            return (bool) constant($name);
        }
        $env_value = getenv($name);
        return in_array(strtolower((string) $env_value), array('1', 'true', 'yes', 'on'), true);
    }

    private function aysa_agent8v3_beta_flag($name, $default = false) {
        $name = strtoupper(preg_replace('/[^A-Z0-9_]/i', '', (string) $name));
        if ($name === '') {
            return (bool) $default;
        }
        if (defined($name)) {
            return (bool) constant($name);
        }
        $env_value = getenv($name);
        if ($env_value === false || $env_value === '') {
            return (bool) $default;
        }
        return in_array(strtolower((string) $env_value), array('1', 'true', 'yes', 'on'), true);
    }

    private function aysa_agent8v3_beta_action_block($agent_action, array $payload) {
        if (!$this->aysa_agent8v3_beta_flag('AYSA_PUBLIC_BETA_SAFETY_MODE', true)) {
            return array();
        }

        $agent_action = sanitize_key((string) $agent_action);
        $gbp_writes = array(
            'create_offpage_gbp_post',
            'retry_offpage_gbp_social_post',
            'reply_offpage_gbp_review',
            'upload_offpage_gbp_media',
            'update_offpage_gbp_business_info',
            'update_offpage_gbp_hours',
            'update_offpage_gbp_attributes',
            'update_offpage_gbp_services',
        );
        if (in_array($agent_action, $gbp_writes, true) && !$this->aysa_agent8v3_beta_flag('AYSA_GBP_WRITES_ENABLED', false)) {
            return array(
                'code' => 'beta_gbp_writes_disabled',
                'message' => 'GBP write actions are disabled for controlled beta unless AYSA_GBP_WRITES_ENABLED is explicitly enabled.',
                'flag' => 'AYSA_GBP_WRITES_ENABLED',
            );
        }

        $adverlink_orders = array(
            'publish_offpage_adverlink_article',
            'approve_offpage_link_building_cart',
            'place_offpage_link_insertion_order',
            'place_offpage_seo_text_ads_order',
        );
        if (in_array($agent_action, $adverlink_orders, true) && !$this->aysa_agent8v3_beta_flag('AYSA_ADVERLINK_ORDERING_ENABLED', false)) {
            return array(
                'code' => 'beta_adverlink_ordering_disabled',
                'message' => 'Adverlink ordering/publishing is disabled for controlled beta unless AYSA_ADVERLINK_ORDERING_ENABLED is explicitly enabled.',
                'flag' => 'AYSA_ADVERLINK_ORDERING_ENABLED',
            );
        }

        $link_exchange_external = array(
            'request_offpage_link_exchange',
            'set_offpage_link_exchange_autoactivation',
            'accept_offpage_exchange_request',
            'insert_offpage_exchange_partner_box',
            'simulate_accept_offpage_exchange_request',
        );
        if (in_array($agent_action, $link_exchange_external, true) && !$this->aysa_agent8v3_beta_flag('AYSA_LINK_EXCHANGE_AUTOACTIVATION_ENABLED', false)) {
            return array(
                'code' => 'beta_link_exchange_autoactivation_disabled',
                'message' => 'Link Exchange external activation/request actions are disabled for controlled beta unless AYSA_LINK_EXCHANGE_AUTOACTIVATION_ENABLED is explicitly enabled.',
                'flag' => 'AYSA_LINK_EXCHANGE_AUTOACTIVATION_ENABLED',
            );
        }

        if ($agent_action === 'execute_technical_fix_plan') {
            $module = sanitize_key((string) ($payload['module'] ?? ''));
            $operation = sanitize_key((string) ($payload['operation'] ?? ''));
            $sitemap_authority_requested = $module === 'sitemap_robots' && $operation === 'enforce_sitemap_authority';
            $selected_items = is_array($payload['selected_items'] ?? null) ? $payload['selected_items'] : array();
            $accepted_fixes = is_array($payload['accepted_fixes'] ?? null) ? $payload['accepted_fixes'] : array();
            foreach (array_merge($selected_items, $accepted_fixes) as $item) {
                if (!is_array($item)) {
                    continue;
                }
                $item_operation = sanitize_key((string) ($item['operation'] ?? $item['action'] ?? ''));
                if ($module === 'sitemap_robots' && $item_operation === 'enforce_sitemap_authority') {
                    $sitemap_authority_requested = true;
                    break;
                }
            }
            if ($sitemap_authority_requested) {
                $project_id = intval($payload['project_id'] ?? 0);
                if (!$this->aysa_technical_sitemap_authority_apply_gate_enabled($project_id)) {
                    return array(
                        'code' => 'beta_sitemap_authority_apply_disabled',
                        'message' => 'Sitemap Authority apply is disabled for controlled rollout unless AYSA_SITEMAP_AUTHORITY_APPLY_ENABLED is explicitly enabled.',
                        'flag' => 'AYSA_SITEMAP_AUTHORITY_APPLY_ENABLED',
                    );
                }
                return array();
            }
            $dangerous_modules = array('schema', 'redirects', 'sitemap_robots', 'internal_linking');
            $dangerous_ops = array(
                'apply',
                'apply_schema',
                'apply_redirect',
                'mark_gone',
                'save_robots',
                'generate_sitemap',
                'restore_recommended_robots',
                'enforce_sitemap_authority',
                'disable_conflicting_sitemap_providers',
                'apply_internal_linking',
                'internal_linking_apply_bridge',
            );
            if (
                in_array($module, $dangerous_modules, true)
                && ($operation === '' || in_array($operation, $dangerous_ops, true))
                && !$this->aysa_agent8v3_beta_flag('AYSA_TECHNICAL_DESTRUCTIVE_WRITES_ENABLED', false)
            ) {
                return array(
                    'code' => 'beta_technical_writes_disabled',
                    'message' => 'Technical SEO write actions are disabled for controlled beta unless AYSA_TECHNICAL_DESTRUCTIVE_WRITES_ENABLED is explicitly enabled.',
                    'flag' => 'AYSA_TECHNICAL_DESTRUCTIVE_WRITES_ENABLED',
                );
            }
        }

        return array();
    }

    public function handle_agent8_conversation() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }
        if (!$this->aysa_agent8v3_beta_flag('AYSA_LEGACY_AGENT8_ENABLED', false)) {
            wp_send_json_error(array(
                'message' => 'Legacy Agent 8 is disabled. Use Agent8 V3.',
                'code' => 'legacy_agent8_disabled',
            ));
            return;
        }
        $request = $_POST;
        $request['_agent8_execution_path'] = 'sync';
        try {
            $result = $this->aysa_execute_agent8_conversation($request);
        } catch (Throwable $e) {
            $this->aysa_agent8_bridge_log('Agent8 sync execution threw.', array(
                'task' => 'agent8',
                'run_id' => '',
                'project_id' => isset($request['project_id']) ? intval($request['project_id']) : 0,
                'conversation_id' => isset($request['conversation_id']) ? sanitize_text_field(wp_unslash((string) $request['conversation_id'])) : '',
                'source' => 'sync_handler',
                'error' => $e->getMessage(),
            ));
            wp_send_json_error(array(
                'message' => 'Agent8 bridge execution failed: ' . $e->getMessage(),
                'code' => 'agent8_bridge_exception',
            ));
            return;
        }
        if (is_wp_error($result)) {
            wp_send_json_error(array('message' => $result->get_error_message()));
            return;
        }

        $this->aysa_agent8_bridge_send_json_success($result, array(
            'task' => 'agent8',
            'run_id' => '',
            'project_id' => isset($request['project_id']) ? intval($request['project_id']) : 0,
            'conversation_id' => isset($request['conversation_id']) ? sanitize_text_field(wp_unslash((string) $request['conversation_id'])) : '',
            'source' => 'sync_handler',
        ));
    }

    public function handle_agent9_conversation() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }
        if (!$this->aysa_agent8v3_beta_flag('AYSA_LEGACY_AGENT9_ENABLED', false)) {
            wp_send_json_error(array(
                'message' => 'Legacy Agent 9 is disabled. Use Agent8 V3.',
                'code' => 'legacy_agent9_disabled',
            ));
            return;
        }
        $result = $this->aysa_execute_agent9_conversation($_POST);
        if (is_wp_error($result)) {
            wp_send_json_error(array('message' => $result->get_error_message()));
            return;
        }

        wp_send_json_success($result);
    }

    private function aysa_agent8_storage_key($user_id, $site_host) {
        return 'aysa_agent8_session_' . intval($user_id) . '_' . md5((string) $site_host);
    }

    private function aysa_agent9_storage_key($user_id, $site_host) {
        return 'aysa_agent9_session_' . intval($user_id) . '_' . md5((string) $site_host);
    }

    private function aysa_agent8_normalize_conversation_language($value, $default = 'en') {
        $value = strtolower(str_replace('_', '-', trim((string) $value)));
        if ($value === '') {
            return (string) $default;
        }
        $base = explode('-', $value)[0];
        if (is_string($base) && preg_match('/^[a-z]{2}$/', $base)) {
            return $base;
        }
        return (string) $default;
    }

    private function aysa_agent8_load_session($user_id, $site_host, $language) {
        $storage_key = $this->aysa_agent8_storage_key($user_id, $site_host);
        $stored = get_user_meta($user_id, $storage_key, true);
        if (is_array($stored)) {
            return $stored;
        }

        return array(
            'conversation_id' => '',
            'conversation_language' => (string) $language,
            'phase' => 'conversation',
            'current_step' => 'conversation',
            'module' => '',
            'tab' => '',
            'request_epoch' => 0,
            'state' => array(),
            'updated_at' => current_time('mysql'),
        );
    }

    private function aysa_agent9_load_session($user_id, $site_host, $language) {
        $storage_key = $this->aysa_agent9_storage_key($user_id, $site_host);
        $stored = get_user_meta($user_id, $storage_key, true);
        if (is_array($stored)) {
            return $stored;
        }

        return array(
            'conversation_id' => '',
            'conversation_language' => (string) $language,
            'phase' => 'setup',
            'current_step' => 'bootstrap',
            'module' => 'agent9',
            'tab' => 'setup',
            'state' => array(),
            'updated_at' => current_time('mysql'),
        );
    }

    private function aysa_agent8_save_session($user_id, $site_host, $language, $result, $request_epoch = 0) {
        $storage_key = $this->aysa_agent8_storage_key($user_id, $site_host);
        $stored = get_user_meta($user_id, $storage_key, true);
        $stored = is_array($stored) ? $stored : array();
        $stored_epoch = max(0, intval($stored['request_epoch'] ?? 0));
        $incoming_epoch = max(0, intval($request_epoch));
        if ($incoming_epoch > 0 && $stored_epoch > $incoming_epoch) {
            return;
        }
        $effective_language = isset($result['state']['meta']['language'])
            ? $this->aysa_agent8_normalize_conversation_language($result['state']['meta']['language'], $language)
            : $this->aysa_agent8_normalize_conversation_language($language, 'en');
        update_user_meta($user_id, $storage_key, array(
            'conversation_id' => (string) ($result['conversation_id'] ?? ''),
            'conversation_language' => (string) $effective_language,
            'phase' => sanitize_key((string) ($result['phase'] ?? 'conversation')),
            'current_step' => sanitize_key((string) ($result['current_step'] ?? 'conversation')),
            'module' => sanitize_key((string) ($result['module'] ?? '')),
            'tab' => sanitize_key((string) ($result['tab'] ?? '')),
            'request_epoch' => max($stored_epoch, $incoming_epoch),
            'state' => isset($result['state']) && is_array($result['state']) ? $result['state'] : array(),
            'updated_at' => current_time('mysql'),
        ));
    }

    private function aysa_agent9_save_session($user_id, $site_host, $language, $result) {
        $storage_key = $this->aysa_agent9_storage_key($user_id, $site_host);
        $effective_language = isset($result['state']['meta']['language'])
            ? $this->aysa_agent8_normalize_conversation_language($result['state']['meta']['language'], $language)
            : $this->aysa_agent8_normalize_conversation_language($language, 'en');
        update_user_meta($user_id, $storage_key, array(
            'conversation_id' => (string) ($result['conversation_id'] ?? ''),
            'conversation_language' => (string) $effective_language,
            'phase' => sanitize_key((string) ($result['phase'] ?? 'setup')),
            'current_step' => sanitize_key((string) ($result['current_step'] ?? 'bootstrap')),
            'module' => sanitize_key((string) ($result['module'] ?? 'agent9')),
            'tab' => sanitize_key((string) ($result['tab'] ?? 'setup')),
            'state' => isset($result['state']) && is_array($result['state']) ? $result['state'] : array(),
            'updated_at' => current_time('mysql'),
        ));
    }

    private function aysa_agent8_canonical_state_option_key($domain, $project_id) {
        return 'aysa_agent8_state_' . sanitize_key((string) $domain) . '_project_' . intval($project_id);
    }

    private function aysa_agent8_read_canonical_state($domain, $project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array();
        }

        $stored = get_option($this->aysa_agent8_canonical_state_option_key($domain, $project_id), array());
        return is_array($stored) ? $stored : array();
    }

    private function aysa_agent8_write_canonical_state($domain, $project_id, $payload) {
        $project_id = intval($project_id);
        if ($project_id <= 0 || !is_array($payload)) {
            return false;
        }

        $payload['updated_at'] = current_time('mysql');
        update_option($this->aysa_agent8_canonical_state_option_key($domain, $project_id), $payload, false);
        return true;
    }

    private function aysa_agent8_persist_canonical_project_state_patch($project_id, $patch) {
        $project_id = intval($project_id);
        $patch = is_array($patch) ? $patch : array();
        if ($project_id <= 0 || $patch === array()) {
            return array('applied' => false, 'reason' => 'empty_canonical_project_state_patch');
        }

        $result = array(
            'applied' => true,
            'domains' => array(),
        );

        $setup_state = isset($patch['setup_state']) && is_array($patch['setup_state']) ? $patch['setup_state'] : array();
        if ($setup_state !== array()) {
            $user_id = intval(get_option('aysa_user_id', 0));
            if ($user_id <= 0) {
                $user_id = intval($this->get_user_id());
            }
            if ($user_id > 0) {
                $this->aysa_persist_local_setup_state_snapshot($user_id, $setup_state, $project_id);
                $result['domains']['setup_state'] = array(
                    'stored' => true,
                    'keys' => array_values(array_keys($setup_state)),
                );
            }
        }

        $research_state = isset($patch['research_state']) && is_array($patch['research_state']) ? $patch['research_state'] : array();
        if ($research_state !== array()) {
            $this->aysa_agent8_write_canonical_state('research', $project_id, $research_state);
            $result['domains']['research_state'] = array(
                'stored' => true,
                'keys' => array_values(array_keys($research_state)),
            );
        }

        $technical_state = isset($patch['technical_state']) && is_array($patch['technical_state']) ? $patch['technical_state'] : array();
        if ($technical_state !== array()) {
            $this->aysa_agent8_write_canonical_state('technical', $project_id, $technical_state);
            $result['domains']['technical_state'] = array(
                'stored' => true,
                'keys' => array_values(array_keys($technical_state)),
            );
        }

        $onpage_state = isset($patch['onpage_state']) && is_array($patch['onpage_state']) ? $patch['onpage_state'] : array();
        if ($onpage_state !== array()) {
            $this->aysa_agent8_write_canonical_state('onpage', $project_id, $onpage_state);
            $result['domains']['onpage_state'] = array(
                'stored' => true,
                'keys' => array_values(array_keys($onpage_state)),
            );
        }

        $offpage_state = isset($patch['offpage_state']) && is_array($patch['offpage_state']) ? $patch['offpage_state'] : array();
        if ($offpage_state !== array()) {
            $this->aysa_agent8_write_canonical_state('offpage', $project_id, $offpage_state);
            $result['domains']['offpage_state'] = array(
                'stored' => true,
                'keys' => array_values(array_keys($offpage_state)),
            );
        }

        $monitoring_state = isset($patch['monitoring_state']) && is_array($patch['monitoring_state']) ? $patch['monitoring_state'] : array();
        if ($monitoring_state !== array()) {
            $this->aysa_agent8_write_canonical_state('monitoring', $project_id, $monitoring_state);
            $result['domains']['monitoring_state'] = array(
                'stored' => true,
                'keys' => array_values(array_keys($monitoring_state)),
            );
        }

        if (empty($result['domains'])) {
            $result['applied'] = false;
            $result['reason'] = 'no_supported_canonical_state_domains';
        }

        return $result;
    }

    private function aysa_agent8_clear_session($user_id, $site_host) {
        delete_user_meta($user_id, $this->aysa_agent8_storage_key($user_id, $site_host));
    }

    private function aysa_agent9_clear_session($user_id, $site_host) {
        delete_user_meta($user_id, $this->aysa_agent9_storage_key($user_id, $site_host));
    }

    private function aysa_agent8_validate_contract_response($response_result) {
        $response_result = is_array($response_result) ? $response_result : array();
        $status = sanitize_key((string) ($response_result['contract_status'] ?? ''));
        $issue_code = sanitize_key((string) ($response_result['contract_issue_code'] ?? ''));
        $details = isset($response_result['contract_issue_details']) && is_array($response_result['contract_issue_details'])
            ? $response_result['contract_issue_details']
            : array();

        if ($status === 'invalid' && $issue_code !== '') {
            return array(
                'status' => 'invalid',
                'issue_code' => $issue_code,
                'details' => $details,
            );
        }

        $phase = sanitize_key((string) ($response_result['phase'] ?? 'conversation'));
        $current_step = sanitize_key((string) ($response_result['current_step'] ?? 'conversation'));
        $assistant_message = trim((string) ($response_result['assistant_message'] ?? ''));
        $suggested_actions = isset($response_result['suggested_actions']) && is_array($response_result['suggested_actions'])
            ? array_values($response_result['suggested_actions'])
            : array();
        $approval_request = isset($response_result['approval_request']) && is_array($response_result['approval_request'])
            ? $response_result['approval_request']
            : null;

        if ($approval_request !== null && sanitize_key((string) ($approval_request['action_key'] ?? '')) === '') {
            return array(
                'status' => 'invalid',
                'issue_code' => 'invalid_approval_request',
                'details' => array(
                    'reason' => 'approval_request_missing_action_key',
                ),
            );
        }

        $ui_only_review_action_types = array(
            'open_keyword_review' => true,
            'open_research_competitors_review' => true,
            'open_missing_pages_review' => true,
            'open_content_strategy_review' => true,
            'open_technical_overview' => true,
            'open_technical_site_audit_review' => true,
            'open_technical_pagespeed_review' => true,
            'open_technical_schema_review' => true,
            'open_technical_redirects_review' => true,
            'open_technical_sitemap_robots_review' => true,
            'open_technical_internal_linking_review' => true,
        );

        foreach ($suggested_actions as $index => $action) {
            if (!is_array($action)) {
                return array(
                    'status' => 'invalid',
                    'issue_code' => 'invalid_suggested_action_shape',
                    'details' => array(
                        'reason' => 'suggested_action_not_object',
                        'action_index' => intval($index),
                    ),
                );
            }

            $label = trim((string) ($action['label'] ?? ''));
            $type = sanitize_key((string) ($action['type'] ?? ''));
            $action_key = sanitize_key((string) ($action['action_key'] ?? ''));
            $url = trim((string) ($action['url'] ?? ''));
            $message = trim((string) ($action['message'] ?? ''));
            if ($label === '') {
                return array(
                    'status' => 'invalid',
                    'issue_code' => 'invalid_suggested_action_shape',
                    'details' => array(
                        'reason' => 'suggested_action_missing_label',
                        'action_index' => intval($index),
                    ),
                );
            }
            if ($type === 'open_url') {
                if ($url === '') {
                    return array(
                        'status' => 'invalid',
                        'issue_code' => 'invalid_suggested_action_shape',
                        'details' => array(
                            'reason' => 'open_url_missing_url',
                            'action_index' => intval($index),
                        ),
                    );
                }
            } elseif (isset($ui_only_review_action_types[$type])) {
                // UI-only review command emitted by backend; does not require action_key or message.
            } elseif ($action_key === '' && $message === '') {
                return array(
                    'status' => 'invalid',
                    'issue_code' => 'invalid_suggested_action_shape',
                    'details' => array(
                        'reason' => 'non_url_action_missing_action_key_or_message',
                        'action_index' => intval($index),
                    ),
                );
            }
        }

        if ($this->aysa_agent8_message_expects_cta($assistant_message) && empty($suggested_actions) && $approval_request === null) {
            return array(
                'status' => 'invalid',
                'issue_code' => 'missing_suggested_action',
                'details' => array(
                    'reason' => 'assistant_references_button_but_no_actions_present',
                ),
            );
        }

        $activation_actions_visible = false;
        foreach ($suggested_actions as $action) {
            $type = sanitize_key((string) ($action['type'] ?? ''));
            $action_key = sanitize_key((string) ($action['action_key'] ?? ''));
            $url = trim((string) ($action['url'] ?? ''));
            if ($action_key === 'validate_activation_key' || ($type === 'open_url' && $url === 'https://app.aysa.ai/websites')) {
                $activation_actions_visible = true;
                break;
            }
        }

        if ($activation_actions_visible && !in_array($phase, array('activation', 'preactivation'), true) && !in_array($current_step, array('activation', 'preactivation'), true)) {
            return array(
                'status' => 'invalid',
                'issue_code' => 'activation_action_outside_activation',
                'details' => array(
                    'reason' => 'activation_only_action_visible_after_activation',
                ),
            );
        }

        $pre_research_steps = array(
            'activation' => true,
            'preactivation' => true,
            'integrations' => true,
            'general' => true,
            'objectives' => true,
            'seo-settings' => true,
            'competition' => true,
            'tone-of-voice' => true,
            'research-ready' => true,
            'research_ready' => true,
        );
        $onboarding_steps = array(
            'integrations' => true,
            'general' => true,
            'objectives' => true,
            'seo-settings' => true,
            'competition' => true,
            'tone-of-voice' => true,
        );
        $research_actions = array(
            'start_keyword_research' => true,
            'show_keyword_mapping' => true,
            'show_missing_pages' => true,
            'show_content_strategy' => true,
            'show_publishing_calendar' => true,
            'send_missing_pages_to_plan' => true,
        );
        if (
            isset($pre_research_steps[$phase]) ||
            isset($pre_research_steps[$current_step]) ||
            strpos($phase, 'onboarding') === 0 ||
            isset($onboarding_steps[$current_step])
        ) {
            $offending_keys = array();
            foreach ($suggested_actions as $action) {
                $action_key = sanitize_key((string) ($action['action_key'] ?? ''));
                if ($action_key !== '' && isset($research_actions[$action_key])) {
                    $offending_keys[] = $action_key;
                }
            }
            if (!empty($offending_keys)) {
                $issue_code = (isset($pre_research_steps[$phase]) || isset($pre_research_steps[$current_step]))
                    ? 'pre_research_action_mismatch'
                    : 'onboarding_action_mismatch';
                return array(
                    'status' => 'invalid',
                    'issue_code' => $issue_code,
                    'details' => array(
                        'reason' => 'research_actions_visible_before_research_ready',
                        'offending_action_keys' => implode(',', array_values(array_unique($offending_keys))),
                    ),
                );
            }
        }

        return array(
            'status' => '',
            'issue_code' => '',
            'details' => array(),
        );
    }

    private function aysa_agent8_message_expects_cta($assistant_message) {
        $assistant_message = function_exists('mb_strtolower')
            ? mb_strtolower(trim((string) $assistant_message), 'UTF-8')
            : strtolower(trim((string) $assistant_message));
        if ($assistant_message === '') {
            return false;
        }

        $patterns = array(
            'apasă butonul de mai jos',
            'apasa butonul de mai jos',
            'press the button below',
            'click the button below',
            'натисни бутона по-долу',
        );
        foreach ($patterns as $pattern) {
            if (strpos($assistant_message, $pattern) !== false) {
                return true;
            }
        }

        return false;
    }

    private function aysa_agent8_log_contract_issue($response_result) {
        $response_result = is_array($response_result) ? $response_result : array();
        $entry = array(
            'timestamp' => current_time('mysql'),
            'phase' => sanitize_key((string) ($response_result['phase'] ?? '')),
            'current_step' => sanitize_key((string) ($response_result['current_step'] ?? '')),
            'assistant_message' => trim((string) ($response_result['assistant_message'] ?? '')),
            'suggested_actions' => isset($response_result['suggested_actions']) && is_array($response_result['suggested_actions'])
                ? $response_result['suggested_actions']
                : array(),
            'approval_request' => isset($response_result['approval_request']) && is_array($response_result['approval_request'])
                ? $response_result['approval_request']
                : null,
            'conversation_id' => trim((string) ($response_result['conversation_id'] ?? '')),
            'canonical_project_id' => intval($response_result['canonical_project_id'] ?? 0),
            'contract_issue_code' => sanitize_key((string) ($response_result['contract_issue_code'] ?? '')),
            'contract_issue_details' => isset($response_result['contract_issue_details']) && is_array($response_result['contract_issue_details'])
                ? $response_result['contract_issue_details']
                : array(),
        );

        $option_key = 'aysa_agent8_contract_issue_log';
        $existing = get_option($option_key, array());
        $existing = is_array($existing) ? $existing : array();
        array_unshift($existing, $entry);
        if (count($existing) > 50) {
            $existing = array_slice($existing, 0, 50);
        }
        update_option($option_key, $existing, false);

        aysa_debug_log('[AYSA AGENT8 CONTRACT] ' . wp_json_encode($entry));
    }

    private function aysa_agent8_messages_from_state($state) {
        if (!is_array($state) || empty($state['history']) || !is_array($state['history'])) {
            return array();
        }

        $messages = array();
        foreach ($state['history'] as $item) {
            if (!is_array($item)) {
                continue;
            }
            $role = isset($item['role']) && $item['role'] === 'user' ? 'user' : 'assistant';
            $message = isset($item['message']) ? trim((string) $item['message']) : '';
            if ($message === '') {
                continue;
            }
            $messages[] = array(
                'role' => $role,
                'message' => $message,
            );
        }

        return $messages;
    }

    private function aysa_agent8_apply_billing_usage_hint($wallet_snapshot, $action_result) {
        if (!is_array($wallet_snapshot)) {
            $wallet_snapshot = array();
        }
        if (!is_array($action_result)) {
            return $wallet_snapshot;
        }

        $billing = isset($action_result['billing']) && is_array($action_result['billing']) ? $action_result['billing'] : array();
        $actual_cents = max(0, intval($billing['actual_credits_cents'] ?? 0));
        if ($actual_cents <= 0) {
            return $wallet_snapshot;
        }

        $entries = isset($billing['entries']) && is_array($billing['entries']) ? $billing['entries'] : array();
        $first_entry = isset($entries[0]) && is_array($entries[0]) ? $entries[0] : array();

        $wallet_snapshot['latest_usage_amount_cents'] = $actual_cents;
        if (!empty($first_entry['action_key'])) {
            $wallet_snapshot['latest_usage_action_key'] = sanitize_key((string) $first_entry['action_key']);
        }
        if (!empty($first_entry['reference'])) {
            $wallet_snapshot['latest_usage_reference'] = sanitize_text_field((string) $first_entry['reference']);
        }
        if (empty($wallet_snapshot['latest_usage_at'])) {
            $wallet_snapshot['latest_usage_at'] = current_time('mysql');
        }

        return $wallet_snapshot;
    }

    private function aysa_agent8_apply_top_level_billing_usage_hint($wallet_snapshot, $result) {
        if (!is_array($wallet_snapshot)) {
            $wallet_snapshot = array();
        }
        if (!is_array($result)) {
            return $wallet_snapshot;
        }

        $billing = isset($result['billing']) && is_array($result['billing']) ? $result['billing'] : array();
        $actual_cents = max(0, intval($billing['actual_credits_cents'] ?? 0));
        if ($actual_cents <= 0) {
            return $wallet_snapshot;
        }

        $entries = isset($billing['entries']) && is_array($billing['entries']) ? $billing['entries'] : array();
        $first_entry = isset($entries[0]) && is_array($entries[0]) ? $entries[0] : array();

        $wallet_snapshot['latest_usage_amount_cents'] = $actual_cents;
        if (!empty($first_entry['action_key'])) {
            $wallet_snapshot['latest_usage_action_key'] = sanitize_key((string) $first_entry['action_key']);
        }
        if (!empty($first_entry['reference'])) {
            $wallet_snapshot['latest_usage_reference'] = sanitize_text_field((string) $first_entry['reference']);
        }
        $wallet_snapshot['latest_usage_at'] = current_time('mysql');

        return $wallet_snapshot;
    }

    private function aysa_agent8_local_mutation_skips_billing($local_mutation_result) {
        if (!is_array($local_mutation_result)) {
            return false;
        }

        foreach (array('already_done', 'already_applied', 'already_ignored', 'stale_approval', 'approval_identifiers_missing') as $flag) {
            if (!empty($local_mutation_result[$flag])) {
                return true;
            }
        }

        $code = sanitize_key((string) ($local_mutation_result['code'] ?? $local_mutation_result['reason'] ?? ''));
        return in_array($code, array('already_done', 'already_applied', 'already_ignored', 'stale_approval', 'approval_identifiers_missing', 'redirect_rule_exists', 'gone_rule_exists', 'already_ignored'), true);
    }

    private function aysa_agent8_bill_successful_local_mutation($project_id, $action_result, $local_mutation_result) {
        $project_id = intval($project_id);
        if ($project_id <= 0 || !is_array($action_result) || !is_array($local_mutation_result) || empty($local_mutation_result['applied'])) {
            return array();
        }

        $action_key = sanitize_key((string) ($action_result['action_key'] ?? $local_mutation_result['operation'] ?? ''));
        $cost_cents = $this->aysa_agent8_local_mutation_cost_cents($action_key);
        if ($action_key === '' || $cost_cents <= 0) {
            return array();
        }

        $summary = isset($local_mutation_result['summary']) && is_array($local_mutation_result['summary']) ? $local_mutation_result['summary'] : array();
        $reference = 'agent8:local_mutation:' . $action_key . ':' . md5(wp_json_encode($summary));
        if ($this->aysa_wallet_usage_reference_exists($project_id, $reference)) {
            return array(
                'status' => 'skipped',
                'estimated_credits_cents' => 0,
                'actual_credits_cents' => 0,
                'reason' => 'already_done',
                'entries' => array(),
            );
        }

        $this->aysa_wallet_track_action_usage(
            $project_id,
            $action_key,
            $reference,
            array(
                'module' => 'agent8_local_mutation',
                'operation' => sanitize_key((string) ($local_mutation_result['operation'] ?? $action_key)),
            ),
            $cost_cents,
            'agent8/local_mutation/' . $action_key
        );

        return array(
            'status' => 'plugin_wallet_tracked',
            'estimated_credits_cents' => $cost_cents,
            'actual_credits_cents' => $cost_cents,
            'entries' => array(array(
                'action_key' => $action_key,
                'billing_mode' => 'plugin_wallet',
                'cost_cents' => $cost_cents,
                'reference' => $reference,
            )),
        );
    }

    private function aysa_agent8_local_mutation_cost_cents($action_key) {
        $action_key = sanitize_key((string) $action_key);
        $costs = array(
            'apply_onpage_meta_batch' => 18,
            'apply_meta' => 18,
            'apply_onpage_rewrite_batch' => 28,
            'apply_rewrite' => 28,
            'apply_onpage_meta_for_page' => 10,
            'apply_onpage_rewrite_for_page' => 20,
            'apply_technical_redirect_candidate' => 12,
            'mark_technical_gone_candidate' => 12,
            'ignore_technical_404_candidate' => 12,
            'apply_technical_redirects_batch' => 18,
            'mark_technical_gone_batch' => 18,
            'ignore_technical_404_batch' => 18,
        );

        return intval($costs[$action_key] ?? 0);
    }

    private function aysa_agent8_runtime_bundle($project_id, $requested_keys = null) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array();
        }

        $setup_user_id = intval($this->aysa_get_effective_setup_user_id());
        $keys = is_array($requested_keys) && !empty($requested_keys)
            ? array_values(array_unique(array_filter(array_map('strval', $requested_keys))))
            : array(
                'setup_snapshot',
                'keyword_mapping_workspace',
                'keyword_mapping_review_payload',
                'research_context_bundle',
                'research_snapshot',
                'research_sitemap_competition',
                'missing_pages_workspace',
                'content_strategy_workspace',
                'existing_missing_page_titles',
                'content_strategy_rows',
                'content_strategy_meta_map',
                'content_strategy_statuses',
                'content_strategy_next',
                'content_strategy_cron_next_run',
                'publishing_calendar_rows',
                'technical_site_audit',
                'technical_pagespeed',
                'technical_errors',
                'technical_sitemap_robots',
                'technical_schema',
                'technical_schema_markup',
                'technical_internal_linking',
                'technical_legacy_urls',
                'onpage_optimization',
                'onpage_workspace',
                'offpage_gbp_overview',
                'offpage_adverlink_orders',
                'offpage_link_exchange_profile',
                'offpage_backlink_audit',
                'offpage_backlink_strategy',
                'offpage_draft_articles',
                'offpage_published_articles',
                'offpage_gbp_social_calendar',
                'offpage_gbp_social_sources',
                'monitoring_rank_tracking',
                'monitoring_ai_overview',
                'monitoring_query_packs',
            );

        $bundle = array();
        foreach ($keys as $key) {
            switch ($key) {
                case 'setup_snapshot':
                    $bundle[$key] = $this->aysa_agent8_get_setup_snapshot($project_id, $setup_user_id);
                    break;
                case 'keyword_mapping_workspace':
                    $bundle[$key] = $this->aysa_keyword_v2_cache_get($project_id, 'keyword_mapping_v2');
                    break;
                case 'keyword_mapping_review_payload':
                    $bundle[$key] = $this->aysa_agent8_get_keyword_mapping_review_payload($project_id);
                    break;
                case 'research_context_bundle':
                    $bundle[$key] = $this->aysa_collect_research_context_bundle($project_id, $setup_user_id);
                    break;
                case 'research_snapshot':
                    $bundle[$key] = $this->aysa_adjustments_get_research_snapshot($project_id, $setup_user_id);
                    break;
                case 'research_sitemap_competition':
                    $bundle[$key] = $this->aysa_agent8_get_research_sitemap_competition_snapshot($project_id);
                    break;
                case 'missing_pages_workspace':
                    $bundle[$key] = $this->aysa_keyword_v2_cache_get($project_id, 'missing_pages_v2');
                    break;
                case 'content_strategy_workspace':
                    $bundle[$key] = $this->aysa_keyword_v2_cache_get($project_id, 'content_strategy_v2');
                    break;
                case 'existing_missing_page_titles':
                    $bundle[$key] = $this->aysa_agent8_get_existing_missing_page_titles($project_id);
                    break;
                case 'content_strategy_rows':
                    $bundle[$key] = $this->aysa_agent8_get_content_strategy_rows($project_id);
                    break;
                case 'content_strategy_meta_map':
                    $bundle[$key] = $this->aysa_agent8_get_content_strategy_meta_map($project_id);
                    break;
                case 'content_strategy_statuses':
                    $bundle[$key] = $this->aysa_agent8_get_content_strategy_status_map($project_id);
                    break;
                case 'content_strategy_next':
                    $bundle[$key] = $this->aysa_agent8_get_content_strategy_next_item($project_id);
                    break;
                case 'content_strategy_cron_next_run':
                    $bundle[$key] = function_exists('wp_next_scheduled') ? wp_next_scheduled('aysa_process_planned_content_now') : null;
                    break;
                case 'publishing_calendar_rows':
                    $bundle[$key] = $this->aysa_agent8_get_publishing_calendar_rows($project_id);
                    break;
                case 'technical_site_audit':
                    $bundle[$key] = $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id);
                    break;
                case 'technical_pagespeed':
                    $bundle[$key] = $this->aysa_adjustments_get_technical_pagespeed_snapshot($project_id);
                    break;
                case 'technical_errors':
                    $bundle[$key] = $this->aysa_adjustments_get_cached_or_idle_technical_errors_snapshot($project_id);
                    break;
                case 'technical_sitemap_robots':
                    $bundle[$key] = $this->aysa_adjustments_get_technical_sitemap_robots_snapshot($project_id);
                    break;
                case 'technical_schema':
                    $bundle[$key] = $this->aysa_adjustments_get_cached_or_idle_technical_schema_snapshot($project_id);
                    break;
                case 'technical_schema_markup':
                    $bundle[$key] = $this->aysa_adjustments_get_technical_schema_markup_snapshot($project_id);
                    break;
                case 'technical_internal_linking':
                    $bundle[$key] = $this->aysa_adjustments_get_technical_internal_linking_snapshot($project_id);
                    break;
                case 'technical_legacy_urls':
                    $bundle[$key] = $this->aysa_agent8_get_technical_legacy_urls();
                    break;
                case 'onpage_optimization':
                    $bundle[$key] = $this->aysa_adjustments_get_onpage_optimization_snapshot($project_id);
                    break;
                case 'onpage_workspace':
                    $bundle[$key] = $this->aysa_agent8_get_onpage_workspace_snapshot($project_id);
                    break;
                case 'offpage_gbp_overview':
                    $bundle[$key] = $this->aysa_agent8_get_offpage_gbp_overview_snapshot($project_id);
                    break;
                case 'offpage_adverlink_orders':
                    $bundle[$key] = $this->aysa_agent8_get_offpage_orders_snapshot($project_id);
                    break;
                case 'offpage_link_exchange_profile':
                    $bundle[$key] = $this->aysa_agent8_get_offpage_link_exchange_profile_snapshot($project_id);
                    break;
                case 'offpage_backlink_audit':
                    $bundle[$key] = $this->aysa_agent8_get_offpage_backlink_audit_snapshot($project_id);
                    break;
                case 'offpage_backlink_strategy':
                    $bundle[$key] = $this->aysa_agent8_get_offpage_backlink_strategy_snapshot($project_id);
                    break;
                case 'offpage_draft_articles':
                    $bundle[$key] = $this->aysa_agent8_get_offpage_draft_articles_snapshot($project_id);
                    break;
                case 'offpage_published_articles':
                    $bundle[$key] = $this->aysa_agent8_get_offpage_published_articles_snapshot($project_id);
                    break;
                case 'offpage_gbp_social_calendar':
                    $bundle[$key] = $this->aysa_agent8_get_gbp_social_calendar_snapshot($project_id);
                    break;
                case 'offpage_gbp_social_sources':
                    $bundle[$key] = $this->aysa_agent8_get_gbp_social_sources($project_id);
                    break;
                case 'monitoring_rank_tracking':
                    $bundle[$key] = $this->aysa_agent8_get_monitoring_rank_tracking_snapshot($project_id);
                    break;
                case 'monitoring_ai_overview':
                    $bundle[$key] = $this->aysa_agent8_get_monitoring_ai_overview_snapshot($project_id);
                    break;
                case 'monitoring_query_packs':
                    $bundle[$key] = $this->aysa_agent8_get_monitoring_query_packs_snapshot($project_id);
                    break;
            }
        }

        return $this->aysa_agent8_transport_reduce_bundle($bundle);
    }

    private function aysa_agent8_transport_reduce_bundle($bundle) {
        if (!is_array($bundle) || empty($bundle)) {
            return array();
        }

        $reduced = array();
        foreach ($bundle as $key => $value) {
            if ((string) $key === 'technical_site_audit') {
                $reduced[$key] = $this->aysa_agent8_transport_reduce_technical_site_audit_snapshot($value);
                continue;
            }
            $top_level_limit = match ((string) $key) {
                'research_snapshot',
                'research_context_bundle',
                'keyword_mapping_workspace',
                'keyword_mapping_review_payload',
                'missing_pages_workspace',
                'content_strategy_rows',
                'publishing_calendar_rows',
                'offpage_adverlink_orders',
                'offpage_draft_articles',
                'offpage_published_articles',
                'monitoring_rank_tracking',
                'monitoring_ai_overview',
                'monitoring_query_packs' => 20,
                'onpage_workspace' => 500,
                default => 30,
            };
            $reduced[$key] = $this->aysa_agent8_transport_reduce_value($value, 0, $top_level_limit);
        }

        return $reduced;
    }

    private function aysa_agent8_transport_reduce_technical_site_audit_snapshot($snapshot) {
        if (!is_array($snapshot) || empty($snapshot)) {
            return array();
        }

        $reduced = $snapshot;
        if (isset($snapshot['action_bundles']) && is_array($snapshot['action_bundles'])) {
            $reduced['action_bundles'] = array_map(
                array($this, 'aysa_agent8_transport_reduce_site_audit_action_bundle'),
                $snapshot['action_bundles']
            );
        }
        foreach (array('pages', 'exports', 'files', 'overview_rows', 'category_summary', 'page_issue_map', 'export_registry') as $key) {
            if (isset($snapshot[$key]) && is_array($snapshot[$key])) {
                $limit = $key === 'pages' ? 250 : 12;
                $reduced[$key] = $this->aysa_agent8_transport_reduce_value($snapshot[$key], 0, $limit);
            }
        }
        if (isset($snapshot['rows']) && is_array($snapshot['rows'])) {
            $reduced['rows'] = array_map(array($this, 'aysa_agent8_transport_reduce_site_audit_issue_row'), array_slice($snapshot['rows'], 0, 30));
        }
        if (isset($snapshot['image_alt']) && is_array($snapshot['image_alt'])) {
            $image_alt = $snapshot['image_alt'];
            if (isset($image_alt['image_alt_inventory_package']) && is_array($image_alt['image_alt_inventory_package'])) {
                $image_alt['image_alt_inventory_package'] = $this->aysa_agent8_transport_reduce_image_alt_execution_package($image_alt['image_alt_inventory_package']);
            }
            $reduced['image_alt'] = $image_alt;
        }
        if (isset($snapshot['issue_groups']) && is_array($snapshot['issue_groups'])) {
            $reduced['issue_groups'] = array_map(function ($group) {
                if (!is_array($group)) {
                    return array();
                }
                return array(
                    'severity' => sanitize_key((string) ($group['severity'] ?? '')),
                    'label' => sanitize_text_field((string) ($group['label'] ?? '')),
                    'count' => intval($group['count'] ?? 0),
                    'issues' => isset($group['issues']) && is_array($group['issues'])
                        ? array_map(array($this, 'aysa_agent8_transport_reduce_site_audit_issue_row'), array_slice($group['issues'], 0, 8))
                        : array(),
                );
            }, array_slice($snapshot['issue_groups'], 0, 4));
        }

        if (isset($snapshot['conclusion']) && is_array($snapshot['conclusion'])) {
            $conclusion = $snapshot['conclusion'];
            if (isset($conclusion['top_issues']) && is_array($conclusion['top_issues'])) {
                $conclusion['top_issues'] = array_map(array($this, 'aysa_agent8_transport_reduce_site_audit_issue_row'), array_slice($conclusion['top_issues'], 0, 12));
            }
            if (isset($conclusion['what_to_do_first']) && is_array($conclusion['what_to_do_first'])) {
                $conclusion['what_to_do_first'] = $this->aysa_agent8_transport_reduce_value($conclusion['what_to_do_first'], 0, 12);
            }
            if (isset($conclusion['recommended_actions']) && is_array($conclusion['recommended_actions'])) {
                $conclusion['recommended_actions'] = array_map(array($this, 'aysa_agent8_transport_reduce_site_audit_action_bundle'), array_slice($conclusion['recommended_actions'], 0, 12));
            }
            $reduced['conclusion'] = $conclusion;
        }

        return $reduced;
    }

    private function aysa_agent8_transport_reduce_technical_internal_linking_snapshot($snapshot) {
        if (!is_array($snapshot) || empty($snapshot)) {
            return array();
        }

        $reduced = $this->aysa_agent8_transport_reduce_value($snapshot, 0, 60);
        // The workspace repeats rows, run, summary and preview_package. The modal
        // consumes those canonical top-level fields directly, so transporting the
        // duplicate can exceed the signed API request limit without adding evidence.
        unset($reduced['workspace']);
        foreach (array('rows', 'legacy_rows', 'preview_rows') as $key) {
            if (isset($snapshot[$key]) && is_array($snapshot[$key])) {
                $reduced[$key] = $this->aysa_agent8_transport_reduce_value(
                    array_slice($snapshot[$key], 0, 60),
                    0,
                    60
                );
            }
        }
        if (isset($snapshot['preview_package']) && is_array($snapshot['preview_package'])) {
            $reduced['preview_package'] = $this->aysa_agent8_transport_reduce_value(
                $snapshot['preview_package'],
                0,
                60
            );
        }

        return $reduced;
    }

    private function aysa_agent8_transport_reduce_site_audit_action_result($action_result) {
        if (!is_array($action_result) || empty($action_result)) {
            return array();
        }

        $reduced = $action_result;
        if (isset($reduced['workspace_snapshot']) && is_array($reduced['workspace_snapshot'])) {
            $reduced['workspace_snapshot'] = $this->aysa_agent8_transport_reduce_technical_site_audit_snapshot($reduced['workspace_snapshot']);
        }
        if (isset($reduced['ui_command']) && is_array($reduced['ui_command'])) {
            $ui_command = $reduced['ui_command'];
            if (isset($ui_command['snapshot']) && is_array($ui_command['snapshot'])) {
                $ui_command['snapshot'] = $this->aysa_agent8_transport_reduce_technical_site_audit_snapshot($ui_command['snapshot']);
            }
            if (isset($ui_command['bundle']) && is_array($ui_command['bundle'])) {
                $ui_command['bundle'] = $this->aysa_agent8_transport_reduce_site_audit_action_bundle($ui_command['bundle']);
            }
            $reduced['ui_command'] = $ui_command;
        }
        if (isset($reduced['canonical_project_state_patch'])) {
            $reduced['canonical_project_state_patch'] = array();
        }

        return $reduced;
    }

    private function aysa_agent8_transport_reduce_site_audit_issue_row($row) {
        if (!is_array($row) || empty($row)) {
            return array();
        }

        $allowed = array(
            'issue_id',
            'key',
            'issue_type',
            'category',
            'severity',
            'priority_score',
            'priority_label',
            'lifecycle_state',
            'title',
            'title_plain',
            'affected_pages_count',
            'affected_pages',
            'affected_display',
            'impact_plain',
            'first_action_plain',
            'source_export',
            'source_file',
        );

        $reduced = array();
        foreach ($allowed as $key) {
            if (array_key_exists($key, $row)) {
                if ($key === 'affected_pages' && is_array($row[$key])) {
                    $reduced[$key] = array_map(array($this, 'aysa_agent8_transport_reduce_site_audit_affected_page'), array_slice($row[$key], 0, 5));
                    continue;
                }
                $limit = $key === 'affected_pages' ? 5 : 12;
                $reduced[$key] = $this->aysa_agent8_transport_reduce_value($row[$key], 0, $limit);
            }
        }

        return $reduced;
    }

    private function aysa_agent8_transport_reduce_site_audit_affected_page($row) {
        if (!is_array($row) || empty($row)) {
            return array();
        }

        $allowed = array('url', 'page_title', 'status_code', 'indexability', 'canonical_url', 'crawl_depth', 'inlinks', 'outlinks');
        $reduced = array();
        foreach ($allowed as $key) {
            if (array_key_exists($key, $row)) {
                $reduced[$key] = $this->aysa_agent8_transport_reduce_value($row[$key], 0, 6);
            }
        }

        return $reduced;
    }

    private function aysa_agent8_transport_reduce_site_audit_action_bundle($bundle) {
        if (!is_array($bundle) || empty($bundle)) {
            return array();
        }

        $reduced = $this->aysa_agent8_transport_reduce_value($bundle, 0, 20);
        if (isset($bundle['metadata_execution_package']) && is_array($bundle['metadata_execution_package'])) {
            $reduced['metadata_execution_package'] = $this->aysa_agent8_transport_reduce_metadata_execution_package($bundle['metadata_execution_package']);
        }
        if (isset($bundle['redirect_execution_package']) && is_array($bundle['redirect_execution_package'])) {
            $reduced['redirect_execution_package'] = $this->aysa_agent8_transport_reduce_value($bundle['redirect_execution_package'], 0, 40);
        }
        if (isset($bundle['internal_linking_execution_package']) && is_array($bundle['internal_linking_execution_package'])) {
            $reduced['internal_linking_execution_package'] = $this->aysa_agent8_transport_reduce_value($bundle['internal_linking_execution_package'], 0, 40);
        }
        if (isset($bundle['image_alt_execution_package']) && is_array($bundle['image_alt_execution_package'])) {
            $reduced['image_alt_execution_package'] = $this->aysa_agent8_transport_reduce_image_alt_execution_package($bundle['image_alt_execution_package']);
        }
        if (isset($bundle['canonical_execution_package']) && is_array($bundle['canonical_execution_package'])) {
            $reduced['canonical_execution_package'] = $this->aysa_agent8_transport_reduce_canonical_execution_package($bundle['canonical_execution_package']);
        }

        return $reduced;
    }

    private function aysa_agent8_transport_reduce_metadata_execution_package($package) {
        if (!is_array($package) || empty($package)) {
            return array();
        }

        $reduced = $package;
        foreach (array('items', 'skipped') as $key) {
            $rows = isset($package[$key]) && is_array($package[$key]) ? $package[$key] : array();
            $reduced[$key] = array_map(array($this, 'aysa_agent8_transport_reduce_metadata_execution_row'), array_slice($rows, 0, 50));
        }
        if (isset($package['count_reconciliation']) && is_array($package['count_reconciliation'])) {
            $reduced['count_reconciliation'] = $this->aysa_agent8_transport_reduce_value($package['count_reconciliation'], 0, 40);
        }

        return $reduced;
    }

    private function aysa_agent8_transport_reduce_metadata_route_evidence_posts($posts) {
        if (!is_array($posts) || empty($posts)) {
            return array();
        }
        $reduced = array();
        foreach ($posts as $url => $post) {
            if (!is_array($post)) {
                continue;
            }
            $clean_url = esc_url_raw((string) $url);
            if ($clean_url === '') {
                $clean_url = esc_url_raw((string) ($post['permalink'] ?? ''));
            }
            if ($clean_url === '') {
                continue;
            }
            $reduced[$clean_url] = array(
                'post_id' => intval($post['post_id'] ?? 0),
                'post_type' => sanitize_key((string) ($post['post_type'] ?? '')),
                'post_status' => sanitize_key((string) ($post['post_status'] ?? '')),
                'post_title' => sanitize_text_field((string) ($post['post_title'] ?? '')),
                'current_meta_title' => sanitize_text_field((string) ($post['current_meta_title'] ?? '')),
                'current_meta_description' => sanitize_text_field((string) ($post['current_meta_description'] ?? '')),
                'current_provider_meta_title' => sanitize_text_field((string) ($post['current_provider_meta_title'] ?? '')),
                'current_provider_meta_description' => sanitize_text_field((string) ($post['current_provider_meta_description'] ?? '')),
                'metadata_provider' => sanitize_key((string) ($post['metadata_provider'] ?? 'aysa')),
                'readback_available' => !empty($post['readback_available']),
                'rollback_available' => !empty($post['rollback_available']),
                'backup_available' => !empty($post['backup_available']),
                'restore_available' => !empty($post['restore_available']),
                'billing_action_key' => sanitize_key((string) ($post['billing_action_key'] ?? '')),
                'idempotency_supported' => !empty($post['idempotency_supported']),
                'reason' => sanitize_key((string) ($post['reason'] ?? '')),
                'permalink' => esc_url_raw((string) ($post['permalink'] ?? $clean_url)),
            );
        }
        return $reduced;
    }

    private function aysa_agent8_transport_reduce_metadata_execution_row($row) {
        if (!is_array($row) || empty($row)) {
            return array();
        }

        $allowed = array(
            'url',
            'field',
            'old_value',
            'current_value',
            'new_value',
            'proposed_value',
            'overwrite_decision',
            'overwrite_reason',
            'quality_flags',
            'confidence',
            'post_id',
            'post_title',
            'issue_type',
            'source',
            'source_export',
            'notes',
            'status',
        );

        $reduced = array();
        foreach ($allowed as $key) {
            if (array_key_exists($key, $row)) {
                $reduced[$key] = $this->aysa_agent8_transport_reduce_value($row[$key], 0, 20);
            }
        }

        return $reduced;
    }

    private function aysa_agent8_transport_metadata_preview_rows_from_route_evidence_posts($posts) {
        if (!is_array($posts) || empty($posts)) {
            return array();
        }

        $rows = array();
        foreach ($posts as $url => $post) {
            if (!is_array($post) || count($rows) >= 250) {
                continue;
            }
            $post_id = intval($post['post_id'] ?? 0);
            if ($post_id <= 0) {
                continue;
            }
            $clean_url = esc_url_raw((string) $url);
            if ($clean_url === '') {
                $clean_url = esc_url_raw((string) ($post['permalink'] ?? ''));
            }
            if ($clean_url === '') {
                continue;
            }
            if (!$this->aysa_agent8v3_metadata_route_evidence_url_is_indexable($clean_url, $post_id)) {
                continue;
            }
            $provider = sanitize_text_field((string) ($post['metadata_provider'] ?? 'aysa'));
            $base = array(
                'url' => $clean_url,
                'post_id' => $post_id,
                'post_type' => sanitize_key((string) ($post['post_type'] ?? '')),
                'post_status' => sanitize_key((string) ($post['post_status'] ?? '')),
                'page_title' => sanitize_text_field((string) ($post['post_title'] ?? '')),
                'provider' => $provider !== '' ? $provider : 'aysa',
                'route_contract' => 'technical_metadata_apply_bridge',
                'readback_available' => !empty($post['readback_available']),
                'rollback_available' => !empty($post['rollback_available']),
                'backup_available' => !empty($post['backup_available']),
                'restore_available' => !empty($post['restore_available']),
                'billing_action_key' => sanitize_key((string) ($post['billing_action_key'] ?? 'agent8v3_metadata_apply')),
                'idempotency_supported' => !empty($post['idempotency_supported']),
                'source' => 'wordpress_route_evidence',
                'proposal_status' => 'needs_generation',
                'review_only' => true,
            );
            $post_object = (object) array(
                'ID' => $post_id,
                'post_title' => (string) ($post['post_title'] ?? ''),
                'post_excerpt' => (string) ($post['post_excerpt'] ?? ''),
                'post_content' => (string) ($post['post_content'] ?? ''),
                'primary_keyword' => (string) ($post['primary_keyword'] ?? ''),
                'main_keyword' => (string) ($post['primary_keyword'] ?? ''),
                'secondary_keywords' => is_array($post['secondary_keywords'] ?? null) ? $post['secondary_keywords'] : array(),
            );

            $title = array_key_exists('current_provider_meta_title', $post)
                ? trim((string) ($post['current_provider_meta_title'] ?? ''))
                : trim((string) ($post['current_meta_title'] ?? $post['post_title'] ?? ''));
            $title_length = function_exists('mb_strlen') ? mb_strlen($title) : strlen($title);
            if ($title === '') {
                $proposed = $this->aysa_technical_site_audit_proposed_meta_title($post_object);
                $validation = $this->aysa_technical_site_audit_validate_metadata_proposal('meta_title', $proposed);
                $rows[] = array_merge($base, array(
                    'field' => 'title',
                    'current_value' => '',
                    'proposed_value' => !empty($validation['ok']) ? $proposed : '',
                    'issue_type' => 'missing_title',
                    'reason' => 'title_missing_from_wordpress_route_evidence',
                    'proposal_status' => !empty($validation['ok']) ? 'generated' : 'needs_generation',
                ));
            } elseif ($title_length > 60 || ($title_length > 0 && $title_length < 25)) {
                $proposed = $this->aysa_technical_site_audit_proposed_meta_title($post_object);
                $validation = $this->aysa_technical_site_audit_validate_metadata_proposal('meta_title', $proposed);
                $rows[] = array_merge($base, array(
                    'field' => 'title',
                    'current_value' => sanitize_text_field($title),
                    'proposed_value' => !empty($validation['ok']) && $proposed !== $title ? $proposed : '',
                    'issue_type' => $title_length > 60 ? 'title_too_long' : 'title_too_short',
                    'reason' => 'title_length_review_from_wordpress_route_evidence',
                    'proposal_status' => !empty($validation['ok']) && $proposed !== $title ? 'generated' : 'needs_generation',
                ));
            }

            if (count($rows) >= 250) {
                break;
            }
            $description = array_key_exists('current_provider_meta_description', $post)
                ? trim((string) ($post['current_provider_meta_description'] ?? ''))
                : trim((string) ($post['current_meta_description'] ?? ''));
            $description_length = function_exists('mb_strlen') ? mb_strlen($description) : strlen($description);
            if ($description === '') {
                $proposed = $this->aysa_technical_site_audit_proposed_meta_description($post_object);
                $validation = $this->aysa_technical_site_audit_validate_metadata_proposal('meta_description', $proposed);
                $rows[] = array_merge($base, array(
                    'field' => 'meta_description',
                    'current_value' => '',
                    'proposed_value' => !empty($validation['ok']) ? $proposed : '',
                    'issue_type' => 'missing_meta_description',
                    'reason' => 'meta_description_missing_from_wordpress_route_evidence',
                    'proposal_status' => !empty($validation['ok']) ? 'generated' : 'needs_generation',
                ));
            } elseif ($description_length > 160 || ($description_length > 0 && $description_length < 70)) {
                $proposed = $this->aysa_technical_site_audit_proposed_meta_description($post_object);
                $validation = $this->aysa_technical_site_audit_validate_metadata_proposal('meta_description', $proposed);
                $rows[] = array_merge($base, array(
                    'field' => 'meta_description',
                    'current_value' => sanitize_text_field($description),
                    'proposed_value' => !empty($validation['ok']) && $proposed !== $description ? $proposed : '',
                    'issue_type' => $description_length > 160 ? 'description_too_long' : 'description_too_short',
                    'reason' => 'meta_description_length_review_from_wordpress_route_evidence',
                    'proposal_status' => !empty($validation['ok']) && $proposed !== $description ? 'generated' : 'needs_generation',
                ));
            }
        }

        return array_values($rows);
    }

    private function aysa_agent8v3_metadata_route_evidence_url_is_indexable($url, $post_id = 0) {
        $url = esc_url_raw((string) $url);
        $post_id = intval($post_id);
        if ($url === '' || (string) wp_parse_url($url, PHP_URL_QUERY) !== '' || (string) wp_parse_url($url, PHP_URL_FRAGMENT) !== '') {
            return false;
        }
        $path = strtolower((string) wp_parse_url($url, PHP_URL_PATH));
        if (strpos($path, '/wp-admin') !== false || strpos($path, '/wp-json') !== false || strpos($path, '/feed') !== false) {
            return false;
        }
        if ($post_id <= 0) {
            return true;
        }
        if ((string) get_post_status($post_id) !== 'publish') {
            return false;
        }
        $post_type = sanitize_key((string) get_post_type($post_id));
        $post_type_object = $post_type !== '' ? get_post_type_object($post_type) : null;
        if (!$post_type_object || empty($post_type_object->public) || ($post_type !== 'page' && empty($post_type_object->publicly_queryable))) {
            return false;
        }
        if ($this->aysa_technical_sitemap_post_type_is_excluded($post_type)) {
            return false;
        }
        $permalink = get_permalink($post_id);
        $permalink = is_string($permalink) ? esc_url_raw($permalink) : '';
        return $permalink !== '' && untrailingslashit($url) === untrailingslashit($permalink);
    }

    private function aysa_agent8_transport_reduce_image_alt_execution_package($package) {
        if (!is_array($package) || empty($package)) {
            return array();
        }

        $reduced = $package;
        foreach (array('items', 'skipped') as $key) {
            $rows = isset($package[$key]) && is_array($package[$key]) ? $package[$key] : array();
            $reduced[$key] = array_map(array($this, 'aysa_agent8_transport_reduce_image_alt_execution_row'), array_slice($rows, 0, 50));
        }

        return $reduced;
    }

    private function aysa_agent8_transport_reduce_image_alt_execution_row($row) {
        if (!is_array($row) || empty($row)) {
            return array();
        }

        $allowed = array(
            'decision',
            'reason',
            'image_url',
            'attachment_id',
            'page_url',
            'page_title',
            'image_filename',
            'old_value',
            'current_value',
            'existing_alt',
            'proposed_value',
            'image_description',
            'confidence',
            'is_decorative',
            'context_alignment',
            'quality_flags',
            'status',
            'candidate_quality',
            'current_alt_quality',
            'image_alt_bucket',
            'alt_quality_reason',
            'inventory_source',
            'inventory_sources',
            'write_supported',
            'media_optimization_eligible',
            'mime_type',
        );

        $reduced = array();
        foreach ($allowed as $key) {
            if (array_key_exists($key, $row)) {
                $reduced[$key] = $this->aysa_agent8_transport_reduce_value($row[$key], 0, 20);
            }
        }

        return $reduced;
    }

    private function aysa_agent8_transport_reduce_canonical_execution_package($package) {
        if (!is_array($package) || empty($package)) {
            return array();
        }

        $reduced = $package;
        foreach (array('items', 'skipped') as $key) {
            $rows = isset($package[$key]) && is_array($package[$key]) ? $package[$key] : array();
            $reduced[$key] = array_map(array($this, 'aysa_agent8_transport_reduce_canonical_execution_row'), array_slice($rows, 0, 50));
        }

        return $reduced;
    }

    private function aysa_agent8_transport_reduce_canonical_execution_row($row) {
        if (!is_array($row) || empty($row)) {
            return array();
        }

        $allowed = array(
            'decision',
            'recommended_action',
            'reason',
            'quality_flags',
            'url',
            'page_url',
            'post_id',
            'post_title',
            'current_canonical',
            'live_canonical_url',
            'recommended_target_url',
            'canonical_provider',
            'confidence',
            'status',
        );

        $reduced = array();
        foreach ($allowed as $key) {
            if (array_key_exists($key, $row)) {
                $reduced[$key] = $this->aysa_agent8_transport_reduce_value($row[$key], 0, 20);
            }
        }

        return $reduced;
    }

    private function aysa_agent8_transport_reduce_value($value, $depth = 0, $item_limit = 30) {
        if ($depth >= 6) {
            return is_scalar($value) || $value === null ? $value : array();
        }

        if (is_string($value)) {
            return strlen($value) > 1200 ? substr($value, 0, 1200) : $value;
        }

        if (!is_array($value)) {
            return $value;
        }

        $is_list = array_keys($value) === range(0, count($value) - 1);
        if ($is_list) {
            $value = array_slice($value, 0, max(1, intval($item_limit)));
        }

        $reduced = array();
        $count = 0;
        foreach ($value as $key => $item) {
            if (!$is_list && $count >= 80) {
                break;
            }
            $reduced[$key] = $this->aysa_agent8_transport_reduce_value($item, $depth + 1, $item_limit);
            $count++;
        }

        return $reduced;
    }

    private function aysa_agent8_runtime_bundle_for_request($project_id, $mode, $user_message = '', $action_key = '') {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array();
        }

        $mode = (string) $mode;
        $action_key = (string) $action_key;
        $normalized_message = strtolower(remove_accents((string) $user_message));

        if ($mode === 'action' || $mode === 'approval') {
            return match ($action_key) {
                'show_setup_activation',
                'show_setup_google_connectivity',
                'show_setup_business_profile',
                'show_setup_objectives',
                'show_setup_seo_settings',
                'show_setup_competition',
                'show_setup_tone_of_voice' => $this->aysa_agent8_runtime_bundle($project_id, array('setup_snapshot')),
                'show_research_keyword_discovery' => $this->aysa_agent8_runtime_bundle($project_id, array('research_snapshot', 'setup_snapshot')),
                'refresh_research_keyword_discovery' => $this->aysa_agent8_runtime_bundle($project_id, array('research_snapshot', 'research_context_bundle', 'setup_snapshot')),
                'show_research_competitor_gap' => $this->aysa_agent8_runtime_bundle($project_id, array('research_snapshot', 'research_context_bundle')),
                'refresh_research_competitor_gap' => $this->aysa_agent8_runtime_bundle($project_id, array('research_snapshot', 'research_context_bundle')),
                'show_research_competitor_identification' => $this->aysa_agent8_runtime_bundle($project_id, array('research_snapshot', 'research_context_bundle')),
                'refresh_research_competitor_identification' => $this->aysa_agent8_runtime_bundle($project_id, array('research_snapshot', 'research_context_bundle')),
                'show_research_competitor_backlinks' => $this->aysa_agent8_runtime_bundle($project_id, array('research_snapshot', 'research_context_bundle')),
                'refresh_research_competitor_backlinks' => $this->aysa_agent8_runtime_bundle($project_id, array('research_snapshot', 'research_context_bundle')),
                'show_research_sitemap_competition' => $this->aysa_agent8_runtime_bundle($project_id, array('research_sitemap_competition')),
                'refresh_research_sitemap_competition' => $this->aysa_agent8_runtime_bundle($project_id, array('research_sitemap_competition')),
                'show_found_keywords' => $this->aysa_agent8_runtime_bundle($project_id, array('keyword_mapping_workspace')),
                'show_keyword_mapping' => $this->aysa_agent8_runtime_bundle($project_id, array('keyword_mapping_workspace', 'keyword_mapping_review_payload', 'research_context_bundle')),
                'show_missing_pages',
                'reject_missing_page',
                'accept_missing_page_ai_proposal',
                'choose_missing_page_existing_page',
                'keep_missing_page' => $this->aysa_agent8_runtime_bundle($project_id, array('missing_pages_workspace', 'research_context_bundle')),
                'send_missing_pages_to_plan' => $this->aysa_agent8_runtime_bundle($project_id, array('missing_pages_workspace', 'research_context_bundle', 'existing_missing_page_titles')),
                'show_content_strategy',
                'show_planned_content' => $this->aysa_agent8_runtime_bundle($project_id, array(
                    'research_context_bundle',
                    'content_strategy_rows',
                    'content_strategy_workspace',
                    'content_strategy_meta_map',
                    'content_strategy_statuses',
                    'content_strategy_next',
                    'content_strategy_cron_next_run',
                )),
                'show_publishing_calendar' => $this->aysa_agent8_runtime_bundle($project_id, array('publishing_calendar_rows')),
                'show_technical_overview', 'guide_technical_optimization', 'refresh_technical_optimization' => $this->aysa_agent8_runtime_bundle($project_id, array(
                    'technical_site_audit',
                    'technical_pagespeed',
                    'technical_errors',
                    'technical_schema',
                    'technical_schema_markup',
                    'technical_sitemap_robots',
                    'technical_internal_linking',
                    'technical_legacy_urls',
                )),
                'show_technical_site_audit', 'refresh_technical_site_audit', 'prepare_metadata_remediation', 'prepare_response_code_review', 'prepare_indexability_review', 'prepare_internal_linking_plan' => $this->aysa_agent8_runtime_bundle($project_id, array('technical_site_audit', 'technical_legacy_urls')),
                'show_technical_pagespeed', 'refresh_technical_pagespeed', 'export_technical_pagespeed_pdf', 'ask_technical_pagespeed_recipient', 'email_technical_pagespeed_pdf' => $this->aysa_agent8_runtime_bundle($project_id, array('technical_pagespeed', 'technical_legacy_urls')),
                'show_technical_schema',
                'apply_technical_schema_recommended',
                'disable_technical_schema' => $this->aysa_agent8_runtime_bundle($project_id, array('technical_schema', 'technical_schema_markup', 'technical_legacy_urls')),
                'show_technical_redirects_review',
                'apply_technical_redirect_candidate',
                'mark_technical_gone_candidate',
                'ignore_technical_404_candidate',
                'apply_technical_redirects_batch',
                'mark_technical_gone_batch',
                'ignore_technical_404_batch',
                'show_technical_redirect_conflicts' => $this->aysa_agent8_runtime_bundle($project_id, array('technical_errors', 'technical_legacy_urls')),
                'show_technical_sitemap_robots', 'refresh_technical_sitemap_robots', 'unblock_technical_robots', 'generate_aysa_sitemap', 'save_technical_robots', 'restore_recommended_robots', 'enforce_aysa_sitemap_authority', 'disable_conflicting_sitemap_providers' => $this->aysa_agent8_runtime_bundle($project_id, array('technical_sitemap_robots', 'technical_legacy_urls')),
                'show_technical_internal_linking', 'refresh_technical_internal_linking', 'preview_technical_internal_linking', 'apply_technical_internal_linking' => $this->aysa_agent8_runtime_bundle($project_id, array('technical_internal_linking', 'technical_legacy_urls')),
                'show_onpage_optimization',
                'show_onpage_meta_generation',
                'show_onpage_keyword_association',
                'accept_onpage_keyword_association',
                'reject_onpage_keyword_association',
                'show_onpage_keyword_validation',
                'show_onpage_rewrite_generation',
                'optimize_onpage_pages',
                'generate_onpage_meta',
                'validate_onpage_keywords',
                'decide_onpage_rewrite',
                'generate_onpage_rewrite' => $this->aysa_agent8_runtime_bundle($project_id, array('onpage_optimization', 'onpage_workspace')),
                'show_offpage_google_business_profile',
                'set_offpage_gbp_primary_location',
                'refresh_offpage_gbp_reviews',
                'refresh_offpage_gbp_performance',
                'create_offpage_gbp_post',
                'reply_offpage_gbp_review',
                'upload_offpage_gbp_media',
                'update_offpage_gbp_business_info',
                'update_offpage_gbp_hours',
                'update_offpage_gbp_attributes',
                'update_offpage_gbp_services' => $this->aysa_agent8_runtime_bundle($project_id, array('offpage_gbp_overview')),
                'show_offpage_gbp_social_calendar',
                'refresh_offpage_gbp_social_calendar_status',
                'reject_offpage_gbp_social_post',
                'retry_offpage_gbp_social_post' => $this->aysa_agent8_runtime_bundle($project_id, array('offpage_gbp_social_calendar', 'offpage_gbp_overview')),
                'generate_offpage_gbp_social_calendar' => $this->aysa_agent8_runtime_bundle($project_id, array('offpage_gbp_social_calendar', 'offpage_gbp_social_sources', 'offpage_gbp_overview')),
                'prepare_offpage_link_exchange_request_preflight' => array_merge(
                    $this->aysa_agent8_runtime_bundle($project_id, array('offpage_link_exchange_profile', 'offpage_adverlink_orders')),
                    array('offpage_link_exchange_requests' => $this->aysa_agent8_get_link_exchange_cached_requests_snapshot($project_id))
                ),
                'show_offpage_link_exchange',
                'accept_offpage_exchange_request',
                'pause_offpage_exchange_request',
                'simulate_accept_offpage_exchange_request',
                'insert_offpage_exchange_partner_box',
                'verify_offpage_exchange_request',
                'show_offpage_link_insertion',
                'recommend_offpage_link_insertion',
                'preview_offpage_link_insertion_order',
                'send_offpage_link_insertion_approval_email',
                'place_offpage_link_insertion_order',
                'show_offpage_seo_text_ads',
                'recommend_offpage_seo_text_ads',
                'preview_offpage_seo_text_ads_order',
                'send_offpage_seo_text_ads_approval_email',
                'place_offpage_seo_text_ads_order' => $this->aysa_agent8_runtime_bundle($project_id, array('offpage_link_exchange_profile', 'offpage_adverlink_orders')),
                'show_link_building_run',
                'approve_link_opportunity',
                'reject_link_opportunity' => $this->aysa_agent8_runtime_bundle($project_id, array('offpage_link_exchange_profile', 'offpage_adverlink_orders', 'offpage_backlink_audit', 'offpage_draft_articles', 'offpage_published_articles')),
                'show_offpage_backlink_audit',
                'run_offpage_backlink_audit',
                'refresh_offpage_backlink_audit_status' => $this->aysa_agent8_runtime_bundle($project_id, array('offpage_backlink_audit', 'offpage_backlink_strategy')),
                'generate_offpage_backlink_strategy',
                'refresh_offpage_backlink_strategy_status',
                'generate_offpage_adverlink_article',
                'refresh_offpage_adverlink_article_status',
                'publish_offpage_adverlink_article',
                'queue_offpage_publisher_article_order',
                'preview_offpage_link_building_cart',
                'approve_offpage_link_building_cart' => $this->aysa_agent8_runtime_bundle($project_id, array('offpage_backlink_audit', 'offpage_backlink_strategy', 'offpage_link_exchange_profile', 'offpage_adverlink_orders', 'research_snapshot', 'onpage_workspace')),
                'show_offpage_draft_articles',
                'preview_offpage_draft_article',
                'send_offpage_draft_article_approval_email',
                'approve_offpage_draft_article' => $this->aysa_agent8_runtime_bundle($project_id, array('offpage_draft_articles', 'offpage_published_articles', 'offpage_adverlink_orders')),
                'show_offpage_published_articles' => $this->aysa_agent8_runtime_bundle($project_id, array('offpage_published_articles', 'offpage_adverlink_orders')),
                'show_monitoring_rank_tracking' => $this->aysa_agent8_runtime_bundle($project_id, array('monitoring_rank_tracking')),
                'refresh_monitoring_rank_tracking' => $this->aysa_agent8_runtime_bundle($project_id, array('monitoring_rank_tracking')),
                'show_monitoring_ai_monitoring' => $this->aysa_agent8_runtime_bundle($project_id, array('monitoring_ai_overview')),
                'refresh_monitoring_ai_monitoring' => $this->aysa_agent8_runtime_bundle($project_id, array('monitoring_ai_overview')),
                'show_monitoring_keyword_overview' => $this->aysa_agent8_runtime_bundle($project_id, array('monitoring_rank_tracking')),
                'refresh_monitoring_keyword_overview' => $this->aysa_agent8_runtime_bundle($project_id, array('monitoring_rank_tracking')),
                'show_monitoring_query_packs' => $this->aysa_agent8_runtime_bundle($project_id, array('monitoring_query_packs', 'monitoring_ai_overview')),
                'save_monitoring_query_pack' => $this->aysa_agent8_runtime_bundle($project_id, array('monitoring_query_packs', 'monitoring_ai_overview')),
                default => array(),
            };
        }

        if ($mode !== 'message' || $normalized_message === '') {
            return array();
        }

        $keys = array();
        if (
            strpos($normalized_message, 'keyword') !== false
            || strpos($normalized_message, 'cautar') !== false
            || strpos($normalized_message, 'mapping') !== false
            || strpos($normalized_message, 'cluster') !== false
        ) {
            $keys[] = 'keyword_mapping_review_payload';
        }
        if (
            strpos($normalized_message, 'missing pages') !== false
            || strpos($normalized_message, 'pagini lipsa') !== false
            || strpos($normalized_message, 'pages lipsa') !== false
            || strpos($normalized_message, 'trimite in plan') !== false
            || strpos($normalized_message, 'trimite missing') !== false
            || strpos($normalized_message, 'send to plan') !== false
            || strpos($normalized_message, 'pune in plan') !== false
        ) {
            $keys[] = 'missing_pages_workspace';
        }
        if (
            strpos($normalized_message, 'trimite in plan') !== false
            || strpos($normalized_message, 'trimite missing') !== false
            || strpos($normalized_message, 'send to plan') !== false
            || strpos($normalized_message, 'pune in plan') !== false
        ) {
            $keys[] = 'existing_missing_page_titles';
        }
        if (
            strpos($normalized_message, 'content strategy') !== false
            || strpos($normalized_message, 'plan editorial') !== false
            || strpos($normalized_message, 'strategie') !== false
            || strpos($normalized_message, 'articole') !== false
        ) {
            $keys = array_merge($keys, array(
                'content_strategy_rows',
                'content_strategy_meta_map',
                'content_strategy_statuses',
                'content_strategy_next',
                'content_strategy_cron_next_run',
            ));
        }
        if (
            strpos($normalized_message, 'calendar') !== false
            || strpos($normalized_message, 'publicare') !== false
            || strpos($normalized_message, 'publishing') !== false
        ) {
            $keys[] = 'publishing_calendar_rows';
        }
        if (
            strpos($normalized_message, 'technical') !== false
            || strpos($normalized_message, 'tehnic') !== false
            || strpos($normalized_message, 'site audit') !== false
            || strpos($normalized_message, 'pagespeed') !== false
            || strpos($normalized_message, 'page speed') !== false
            || strpos($normalized_message, 'sitemap') !== false
            || strpos($normalized_message, 'robots') !== false
            || strpos($normalized_message, 'internal linking') !== false
            || strpos($normalized_message, 'linkuri interne') !== false
        ) {
            $keys = array_merge($keys, array(
                'technical_site_audit',
                'technical_pagespeed',
                'technical_errors',
                'technical_schema',
                'technical_schema_markup',
                'technical_sitemap_robots',
                'technical_internal_linking',
                'technical_legacy_urls',
            ));
        }
        if (
            strpos($normalized_message, 'on page') !== false
            || strpos($normalized_message, 'on-page') !== false
            || strpos($normalized_message, 'onpage') !== false
            || strpos($normalized_message, 'optimizare pagina') !== false
            || strpos($normalized_message, 'optimizare pagini') !== false
            || strpos($normalized_message, 'page update') !== false
            || strpos($normalized_message, 'rescrie pagina') !== false
        ) {
            $keys = array_merge($keys, array(
                'onpage_optimization',
                'onpage_workspace',
            ));
        }
        if (
            strpos($normalized_message, 'google business') !== false
            || strpos($normalized_message, 'gbp') !== false
            || strpos($normalized_message, 'business profile') !== false
            || strpos($normalized_message, 'off page') !== false
            || strpos($normalized_message, 'off-page') !== false
            || strpos($normalized_message, 'offpage') !== false
        ) {
            $keys[] = 'offpage_gbp_overview';
        }
        if (
            strpos($normalized_message, 'exchange') !== false
            || strpos($normalized_message, 'link exchange') !== false
        ) {
            $keys[] = 'offpage_link_exchange_profile';
            $keys[] = 'offpage_adverlink_orders';
        }
        if (
            strpos($normalized_message, 'backlink') !== false
            || strpos($normalized_message, 'audit offpage') !== false
            || strpos($normalized_message, 'backlink audit') !== false
        ) {
            $keys[] = 'offpage_backlink_audit';
        }
        if (
            strpos($normalized_message, 'draft articles') !== false
            || strpos($normalized_message, 'drafturi') !== false
        ) {
            $keys[] = 'offpage_draft_articles';
        }
        if (
            strpos($normalized_message, 'published articles') !== false
            || strpos($normalized_message, 'articole publicate') !== false
        ) {
            $keys[] = 'offpage_published_articles';
        }
        if (
            strpos($normalized_message, 'monitoring') !== false
            || strpos($normalized_message, 'monitorizare') !== false
            || strpos($normalized_message, 'rank tracking') !== false
            || strpos($normalized_message, 'pozitii') !== false
            || strpos($normalized_message, 'poziții') !== false
            || strpos($normalized_message, 'keyword overview') !== false
            || strpos($normalized_message, 'ai monitoring') !== false
            || strpos($normalized_message, 'query pack') !== false
            || strpos($normalized_message, 'query packs') !== false
        ) {
            $keys = array_merge($keys, array(
                'monitoring_rank_tracking',
                'monitoring_ai_overview',
                'monitoring_query_packs',
            ));
        }

        $keys = array_values(array_unique($keys));
        if (empty($keys)) {
            return array();
        }

        $bundle = $this->aysa_agent8_runtime_bundle($project_id, $keys);
        if (strpos($normalized_message, 'exchange') !== false || strpos($normalized_message, 'schimb de linkuri') !== false) {
            $bundle['offpage_link_exchange_requests'] = $this->aysa_agent8_get_link_exchange_cached_requests_snapshot($project_id);
        }

        return $bundle;
    }

    private function aysa_agent8_get_link_building_targets($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array();
        }

        $targets = array();
        global $wpdb;
        $table = esc_sql($wpdb->prefix . 'aysa_onpage_inventory');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $contexts = $wpdb->get_results(
            $wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT entity_key, entity_group, url, title, publication_state, mapping_state, main_keyword
" .
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
                "                 FROM {$table}
                 WHERE project_id = %d
                   AND publication_state = 'published'
                   AND mapping_state = 'mapped'
                   AND entity_group IN ('product', 'ecommerce_category', 'page', 'article')
                   AND main_keyword IS NOT NULL
                   AND main_keyword <> ''
                 ORDER BY
                   CASE entity_group
                     WHEN 'page' THEN 1
                     WHEN 'ecommerce_category' THEN 2
                     WHEN 'product' THEN 3
                     WHEN 'article' THEN 4
                     ELSE 9
                   END,
                   updated_at DESC
                 LIMIT 150",
                $project_id
            ),
            ARRAY_A
        );
        if (!is_array($contexts)) {
            $contexts = array();
        }
        foreach ($contexts as $context) {
            if (!is_array($context)) {
                continue;
            }
            $entity_group = sanitize_key((string) ($context['entity_group'] ?? ''));
            $publication_state = sanitize_key((string) ($context['publication_state'] ?? ''));
            $mapping_state = sanitize_key((string) ($context['mapping_state'] ?? ''));
            $url = esc_url_raw((string) ($context['url'] ?? ''));
            $keyword = sanitize_text_field((string) ($context['main_keyword'] ?? ''));
            if (!in_array($entity_group, array('product', 'ecommerce_category', 'page', 'article'), true)) {
                continue;
            }
            if ($publication_state !== 'published' || $mapping_state !== 'mapped' || $url === '' || $keyword === '') {
                continue;
            }
            if (strpos(strtolower($keyword), 'qa-kw-') !== false) {
                continue;
            }
            if ($this->aysa_agent8_is_link_building_utility_url($url)) {
                continue;
            }
            $targets[] = array(
                'entity_key' => sanitize_text_field((string) ($context['entity_key'] ?? '')),
                'entity_group' => $entity_group,
                'url' => $url,
                'title' => sanitize_text_field((string) ($context['title'] ?? $context['entity_title'] ?? $keyword)),
                'main_keyword' => $keyword,
                'primary_keyword' => $keyword,
                'secondary_keywords' => array(),
                'mapping_state' => $mapping_state,
                'publication_state' => $publication_state,
            );
        }

        usort($targets, array($this, 'aysa_agent8_link_building_target_priority_compare'));

        return array_slice($targets, 0, 100);
    }

    private function aysa_agent8_get_homepage_link_building_target($project_id) {
        $targets = $this->aysa_agent8_get_link_building_targets($project_id);
        $home_url = rtrim(home_url('/'), '/') . '/';
        foreach ($targets as $target) {
            if (!is_array($target)) {
                continue;
            }
            $url = rtrim((string) ($target['url'] ?? ''), '/') . '/';
            if ($url === $home_url) {
                return array(
                    'url' => esc_url_raw((string) ($target['url'] ?? $home_url)),
                    'primary_keyword' => sanitize_text_field((string) ($target['primary_keyword'] ?? $target['main_keyword'] ?? get_bloginfo('name'))),
                    'title' => sanitize_text_field((string) ($target['title'] ?? get_bloginfo('name'))),
                );
            }
        }
        return array(
            'url' => esc_url_raw($home_url),
            'primary_keyword' => sanitize_text_field((string) get_bloginfo('name')),
            'title' => sanitize_text_field((string) get_bloginfo('name')),
        );
    }

    private function aysa_agent8_link_building_target_priority_compare($a, $b) {
        $a_priority = $this->aysa_agent8_link_building_target_priority(is_array($a) ? $a : array());
        $b_priority = $this->aysa_agent8_link_building_target_priority(is_array($b) ? $b : array());
        if ($a_priority === $b_priority) {
            return strcmp((string) ($a['url'] ?? ''), (string) ($b['url'] ?? ''));
        }
        return $a_priority <=> $b_priority;
    }

    private function aysa_agent8_link_building_target_priority($target) {
        $url = (string) ($target['url'] ?? '');
        $path = rtrim((string) wp_parse_url($url, PHP_URL_PATH), '/');
        if ($path === '') {
            return 0;
        }
        $entity_group = sanitize_key((string) ($target['entity_group'] ?? ''));
        if ($entity_group === 'page') {
            return $this->aysa_agent8_is_priority_link_building_page($target) ? 1 : 5;
        }
        if ($entity_group === 'ecommerce_category') {
            return 2;
        }
        if ($entity_group === 'product') {
            return 3;
        }
        if ($entity_group === 'article') {
            return 4;
        }
        return 9;
    }

    private function aysa_agent8_is_priority_link_building_page($target) {
        $path = strtolower((string) wp_parse_url((string) ($target['url'] ?? ''), PHP_URL_PATH));
        $title = (string) ($target['title'] ?? '');
        if (preg_match('#^/\d+-\d+/?$#', $path) || preg_match('/^Post #\d+$/', $title)) {
            return false;
        }
        $haystack = strtolower(trim(
            $path . ' ' .
            (string) ($target['main_keyword'] ?? '') . ' ' .
            (string) ($target['primary_keyword'] ?? '') . ' ' .
            $title
        ));
        if (strpos($haystack, 'qa-kw-') !== false) {
            return false;
        }
        foreach (array('florarie', 'flori', 'buchet', 'buchete', 'aranjament', 'aranjamente', 'livrare', 'online', 'shop', 'magazin', 'servicii', 'service', 'produse', 'product', 'pret', 'preturi', 'oferta', 'oferte', 'plante', 'ghivece', 'jardiniere') as $needle) {
            if (strpos($haystack, $needle) !== false) {
                return true;
            }
        }
        return false;
    }

    private function aysa_agent8_is_link_building_utility_url($url) {
        $path = strtolower((string) wp_parse_url((string) $url, PHP_URL_PATH));
        if ($path === '') {
            return false;
        }
        foreach (array('/checkout', '/cart', '/cos', '/account', '/my-account', '/cont', '/login', '/autentificare', '/wishlist', '/contact', '/privacy', '/terms', '/delivery', '/return', '/refund', '/shipping', '/sample-page', '/search', '/feed', '/tag/', '/author/', '/wp-admin', '/wp-json') as $needle) {
            if (strpos($path, $needle) !== false) {
                return true;
            }
        }
        return false;
    }

    private function aysa_agent8_get_onpage_workspace_snapshot($project_id, $filters = array(), $page = 1, $per_page = 25) {
        $service = $this->aysa_onpage_workspace_service();
        if (!$service) {
            return array(
                'summary' => array(),
                'rows' => array(),
                'run' => array(),
                'active_entity_group' => 'page',
            );
        }

        $project_id = intval($project_id);
        $filters = is_array($filters) ? $filters : array();
        $page = max(1, intval($page));
        $per_page = max(5, min(100, intval($per_page)));
        $workspace = null;
        $all_rows = array();
        $seen = array();
        $group_counts = array();
        $entity_filter = sanitize_key((string) ($filters['entity_type'] ?? 'all'));
        $entity_groups = array('product', 'ecommerce_category', 'page', 'article', 'other');
        $entity_group_map = array(
            'onpage_entity_product' => 'product',
            'entity_product' => 'product',
            'onpage_entity_ecommerce_category' => 'ecommerce_category',
            'entity_category' => 'ecommerce_category',
            'onpage_entity_page' => 'page',
            'entity_page' => 'page',
            'onpage_entity_article' => 'article',
            'entity_article' => 'article',
            'onpage_entity_other' => 'other',
            'entity_other' => 'other',
            'product' => 'product',
            'ecommerce_category' => 'ecommerce_category',
            'page' => 'page',
            'article' => 'article',
            'other' => 'other',
        );
        if (isset($entity_group_map[$entity_filter])) {
            $entity_groups = array($entity_group_map[$entity_filter]);
        }

        $workspace_filters = array();
        $active_tab = sanitize_key((string) ($filters['tab'] ?? 'keyword_mapping'));
        $search = trim((string) ($filters['search'] ?? $filters['q'] ?? ''));
        if ($search !== '') {
            $workspace_filters['q'] = $search;
        }
        $workflow_status = sanitize_key((string) ($filters['workflow_status'] ?? $filters['status'] ?? ''));
        $view_map = array(
            'mapping_required' => 'keyword_mapping_needed',
            'onpage_mapping_required' => 'keyword_mapping_needed',
            'onpage_mapping_review' => 'mapping_review',
            'generated' => 'meta_ready',
            'applied' => 'meta_applied',
            'onpage_meta_ready' => 'meta_ready',
            'onpage_meta_applied' => 'meta_applied',
            'onpage_content_rewrite' => 'content_rewrite',
            'onpage_apply_queue' => 'apply_queue',
        );
        if (isset($view_map[$workflow_status])) {
            $workspace_filters['view'] = $view_map[$workflow_status];
        } elseif (in_array($active_tab, array('content', 'aeo_content_qa'), true)) {
            // Content and its following AEO review are one bounded workflow,
            // not a second copy of the complete On Page inventory. Keep
            // explicit status filters authoritative, but default both tabs to
            // entities selected in the current content rewrite cycle.
            $workspace_filters['view'] = 'content_rewrite_current';
        }

        $global_offset = ($page - 1) * $per_page;
        $remaining = $per_page;
        $cursor = 0;
        $refresh_inventory = !empty($filters['_refresh_inventory']);
        $business_relevance_context = $this->aysa_adjustments_collect_business_relevance_context(0, $project_id);

        foreach ($entity_groups as $entity_group) {
            $group_workspace = $service->build_workspace($project_id, array(
                'entity_group' => $entity_group,
                'filters' => $workspace_filters,
                'page' => 1,
                'per_page' => 1,
                'refresh' => $refresh_inventory,
            ));
            $refresh_inventory = false;
            if (!is_array($group_workspace)) {
                continue;
            }
            if ($workspace === null) {
                $workspace = $group_workspace;
            }

            $pagination = isset($group_workspace['pagination']) && is_array($group_workspace['pagination']) ? $group_workspace['pagination'] : array();
            $group_total = max(0, intval($pagination['total'] ?? 0));
            $group_counts[$entity_group] = $group_total;
            if ($remaining <= 0 || $group_total <= 0) {
                $cursor += $group_total;
                continue;
            }

            $slice_start = max(0, $global_offset - $cursor);
            if ($slice_start >= $group_total) {
                $cursor += $group_total;
                continue;
            }

            $slice_count = min($remaining, $group_total - $slice_start);
            $group_page = intdiv($slice_start, $per_page) + 1;
            $group_page_offset = $slice_start % $per_page;
            $group_rows = array();
            while (count($group_rows) < ($group_page_offset + $slice_count)) {
                $paged_workspace = $service->build_workspace($project_id, array(
                    'entity_group' => $entity_group,
                    'filters' => $workspace_filters,
                    'page' => $group_page,
                    'per_page' => $per_page,
                    'refresh' => false,
                ));
                if (!is_array($paged_workspace) || !isset($paged_workspace['rows']) || !is_array($paged_workspace['rows'])) {
                    break;
                }
                $paged_rows = array_values($paged_workspace['rows']);
                if ($paged_rows === array()) {
                    break;
                }
                $group_rows = array_merge($group_rows, $paged_rows);
                $group_page++;
            }
            $group_rows = array_slice($group_rows, $group_page_offset, $slice_count);
            $remaining -= count($group_rows);
            $cursor += $group_total;

            foreach ($group_rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $row = $this->aysa_agent8_canonicalize_onpage_row_url($row);
                $dedupe_key = (string) ($row['entity_key'] ?? '');
                if ($dedupe_key === '') {
                    $dedupe_key = untrailingslashit((string) ($row['url'] ?? ''));
                }
                if ($dedupe_key !== '' && isset($seen[$dedupe_key])) {
                    continue;
                }
                if ($dedupe_key !== '') {
                    $seen[$dedupe_key] = true;
                }
                $row = $this->aysa_agent8_hydrate_onpage_metadata_route_evidence_row($row);
                $row = $this->aysa_agent8_hydrate_onpage_applied_meta_row($row);
                $row = $this->aysa_agent8_hydrate_onpage_seo_appendix_row($row);
                $row = $this->aysa_agent8_hydrate_onpage_featured_alt_row($row);
                $row = $this->aysa_agent8_hydrate_onpage_business_relevance_row($row, $business_relevance_context);
                $all_rows[] = $row;
            }
        }

        if ($workspace === null) {
            return array();
        }

        $workspace['rows'] = $all_rows;
        $workspace['pagination'] = array(
            'page' => $page,
            'per_page' => $per_page,
            'total' => array_sum($group_counts),
            'pages' => max(1, (int) ceil(array_sum($group_counts) / max(1, $per_page))),
        );
        $workspace['agent8_full_snapshot'] = false;
        $workspace['agent8_server_paginated'] = true;
        $workspace['active_entity_group'] = 'all';
        $workspace['agent8_entity_group_counts'] = $group_counts;
        $workspace['agent8_mapping_counts'] = in_array($active_tab, array('overview', 'keyword_mapping'), true)
            ? $this->aysa_agent8_get_onpage_mapping_counts($project_id, $filters)
            : array();
        $workspace['agent8_aeo_counts'] = array();

        return $workspace;
    }

    private function aysa_agent8_get_onpage_durable_review_queue($project_id, $limit = 80, $source_run_id = 0) {
        $project_id = intval($project_id);
        $limit = max(1, min(80, intval($limit)));
        $source_run_id = max(0, intval($source_run_id));
        $service = $this->aysa_onpage_workspace_service();
        if ($source_run_id <= 0 && $service && method_exists($service, 'get_latest_provider_output_run_id')) {
            $source_run_id = max(0, intval($service->get_latest_provider_output_run_id($project_id)));
        }
        $reconciliation = $service && $source_run_id > 0 && method_exists($service, 'get_provider_output_reconciliation')
            ? $service->get_provider_output_reconciliation($project_id, $source_run_id)
            : array();
        $reconciliation_rows = isset($reconciliation['rows']) && is_array($reconciliation['rows'])
            ? array_values($reconciliation['rows'])
            : array();
        if ($reconciliation_rows !== array()) {
            $rows = array();
            $business_relevance_context = $this->aysa_adjustments_collect_business_relevance_context(0, $project_id);
            $reconciliation_entity_keys = array_values(array_filter(array_map(static function($output) {
                return is_array($output) ? sanitize_text_field((string) ($output['entity_key'] ?? '')) : '';
            }, $reconciliation_rows)));
            $current_inventory_lifecycle = method_exists($service, 'get_inventory_lifecycle_by_entity_keys')
                ? $service->get_inventory_lifecycle_by_entity_keys($project_id, $reconciliation_entity_keys)
                : array();
            foreach ($reconciliation_rows as $output) {
                if (!is_array($output)) {
                    continue;
                }
                $row = isset($output['inventory_row']) && is_array($output['inventory_row'])
                    ? $output['inventory_row']
                    : array();
                $provider_output_id = sanitize_text_field((string) ($output['provider_output_id'] ?? ''));
                $entity_key = sanitize_text_field((string) ($output['entity_key'] ?? $row['entity_key'] ?? ''));
                $row['entity_key'] = $entity_key;
                $row['item_id'] = $entity_key !== '' ? $entity_key : $provider_output_id;
                $row['stable_row_id'] = $provider_output_id !== '' ? $provider_output_id : $row['item_id'];
                $row['provider_output_id'] = $provider_output_id;
                $row['provider_output_state'] = sanitize_key((string) ($output['state'] ?? 'invalid_with_reason'));
                $row['provider_output_reason_code'] = sanitize_key((string) ($output['reason_code'] ?? ''));
                $row['provider_output_reason'] = sanitize_text_field((string) ($output['reason'] ?? ''));
                $row['duplicate_reference'] = sanitize_text_field((string) ($output['duplicate_reference'] ?? ''));
                $row['source_run_id'] = $source_run_id;
                $has_current_inventory_lifecycle = $entity_key !== '' && isset($current_inventory_lifecycle[$entity_key]);
                if ($has_current_inventory_lifecycle) {
                    $current_row = $current_inventory_lifecycle[$entity_key];
                    foreach (array('entity_id', 'object_kind', 'post_type', 'entity_group', 'publication_state', 'mapping_state', 'main_keyword', 'url', 'title', 'content_excerpt', 'meta_state', 'output_state') as $current_field) {
                        if (array_key_exists($current_field, $current_row)) {
                            $row[$current_field] = $current_row[$current_field];
                        }
                    }
                }
                $row = $this->aysa_agent8_canonicalize_onpage_row_url($row);
                $row = $this->aysa_agent8_hydrate_onpage_metadata_route_evidence_row($row);
                $row = $this->aysa_agent8_hydrate_onpage_applied_meta_row($row);
                $row = $this->aysa_agent8_hydrate_onpage_business_relevance_row($row, $business_relevance_context);
                if (!$has_current_inventory_lifecycle && $entity_key !== '' && method_exists($service, 'get_detail')) {
                    $detail = $service->get_detail($project_id, $entity_key, false);
                    $row = $this->aysa_agent8_reconcile_onpage_provider_output_lifecycle($row, $detail);
                } else {
                    $row = $this->aysa_agent8_reconcile_onpage_provider_output_lifecycle($row, array());
                }
                $rows[] = $row;
            }
            $total = count($rows);
            $counts = $this->aysa_agent8_onpage_provider_output_state_counts($rows);
            $reconciled_count = array_sum(array_map('intval', $counts));
            $generated_count = intval($reconciliation['generated_count'] ?? $total);
            $rows = array_slice($rows, 0, $limit);
            $compact = $this->aysa_agent8_compact_onpage_modal_snapshot(array(
                'rows' => $rows,
                'summary' => array(
                    'source' => 'durable_provider_output_reconciliation',
                    'source_run_id' => $source_run_id,
                    'generated_count' => $generated_count,
                    'reconciled_count' => $reconciled_count,
                    'reconciliation_counts' => $counts,
                    'reconciliation_invariant_passed' => $generated_count === $reconciled_count,
                    'total' => $total,
                    'included' => count($rows),
                    'remaining' => max(0, $total - count($rows)),
                    'selection_scope' => 'durable_review_queue',
                ),
                'pagination' => array(
                    'page' => 1,
                    'per_page' => $limit,
                    'total' => $total,
                    'pages' => max(1, intval(ceil($total / $limit))),
                ),
            ), 'meta_snippets');
            $compact['selection_scope'] = 'durable_review_queue';
            $compact['qa_smoke_mode'] = false;
            return $compact;
        }

        // Compatibility fallback for historical runs that predate per-run reconciliation snapshots.
        $rows = array();
        $seen = array();
        $page = 1;
        $total = 0;
        do {
            $snapshot = $this->aysa_agent8_get_onpage_workspace_snapshot(
                $project_id,
                array('workflow_status' => 'generated', 'tab' => 'meta_snippets'),
                $page,
                100
            );
            $pagination = isset($snapshot['pagination']) && is_array($snapshot['pagination']) ? $snapshot['pagination'] : array();
            foreach ((array) ($snapshot['rows'] ?? array()) as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $row_run_id = intval($row['worker_run_id'] ?? $row['run_id'] ?? 0);
                if ($source_run_id > 0 && $row_run_id !== $source_run_id) {
                    continue;
                }
                $entity_key = trim((string) ($row['entity_key'] ?? ''));
                if ($entity_key === '' || isset($seen[$entity_key])) {
                    continue;
                }
                $seen[$entity_key] = true;
                $total++;
                if (count($rows) >= $limit) {
                    continue;
                }
                $row['item_id'] = $entity_key;
                $row['stable_row_id'] = $entity_key;
                $rows[] = $row;
            }
            $pages = max(1, intval($pagination['pages'] ?? 1));
            $page++;
        } while ($page <= $pages);

        $compact = $this->aysa_agent8_compact_onpage_modal_snapshot(array(
            'rows' => $rows,
            'summary' => array(
                'source' => 'durable_metadata_review_queue',
                'source_run_id' => $source_run_id,
                'total' => $total,
                'included' => count($rows),
                'remaining' => max(0, $total - count($rows)),
                'selection_scope' => 'durable_review_queue',
            ),
            'pagination' => array(
                'page' => 1,
                'per_page' => $limit,
                'total' => $total,
                'pages' => max(1, intval(ceil($total / $limit))),
            ),
        ), 'meta_snippets');
        $compact['selection_scope'] = 'durable_review_queue';
        $compact['qa_smoke_mode'] = false;
        return $compact;
    }

    private function aysa_agent8_reconcile_onpage_provider_output_lifecycle($row, $detail) {
        $row = is_array($row) ? $row : array();
        $detail = is_array($detail) ? $detail : array();
        if (sanitize_key((string) ($row['provider_output_state'] ?? '')) !== 'actionable_review') {
            return $row;
        }

        $history = isset($detail['seo_history']) && is_array($detail['seo_history'])
            ? $detail['seo_history']
            : array();
        $readback = isset($detail['metadata_readback']) && is_array($detail['metadata_readback'])
            ? $detail['metadata_readback']
            : (isset($row['metadata_readback']) && is_array($row['metadata_readback']) ? $row['metadata_readback'] : array());
        $row_applied = sanitize_key((string) ($row['meta_state'] ?? '')) === 'applied'
            || sanitize_key((string) ($row['output_state'] ?? '')) === 'meta_applied';
        $history_applied = intval($history['is_applied'] ?? 0) === 1
            && intval($history['is_reverted'] ?? 0) !== 1
            && sanitize_key((string) ($history['generator_type'] ?? '')) === 'agent8_onpage_meta'
            && sanitize_key((string) ($history['generation_method'] ?? '')) === 'onpage_workspace_apply_meta';
        if ((!$history_applied && !$row_applied) || sanitize_key((string) ($readback['status'] ?? '')) !== 'matched') {
            return $row;
        }

        $package = $this->aysa_agent8_decode_onpage_package_value($row['meta_package_json'] ?? array());
        $proposed_title = trim((string) ($package['meta_title'] ?? $package['title'] ?? ''));
        $proposed_description = trim((string) ($package['meta_description'] ?? $package['description'] ?? ''));
        if ($proposed_title === '' && $proposed_description === '') {
            return $row;
        }

        $normalize = static function($value) {
            $value = html_entity_decode(wp_strip_all_tags((string) $value), ENT_QUOTES | ENT_HTML5, 'UTF-8');
            $value = (string) preg_replace('/\s+/u', ' ', trim($value));
            return function_exists('mb_strtolower') ? mb_strtolower($value, 'UTF-8') : strtolower($value);
        };
        $live_provider = isset($readback['live_provider']) && is_array($readback['live_provider']) ? $readback['live_provider'] : array();
        $history_title = trim((string) ($history['meta_title'] ?? $live_provider['meta_title'] ?? $row['current_meta_title'] ?? ''));
        $history_description = trim((string) ($history['meta_description'] ?? $live_provider['meta_description'] ?? $row['current_meta_description'] ?? ''));
        $title_matches = $proposed_title === '' || $normalize($proposed_title) === $normalize($history_title);
        $description_matches = $proposed_description === '' || $normalize($proposed_description) === $normalize($history_description);
        if (!$title_matches || !$description_matches) {
            return $row;
        }

        $row['provider_output_state'] = 'already_optimized';
        $row['provider_output_reason_code'] = 'metadata_applied_and_verified';
        $row['provider_output_reason'] = 'Metadata generată a fost deja aplicată și verificată prin istoricul On Page.';
        $row['meta_state'] = 'applied';
        $row['output_state'] = 'meta_applied';
        $row['current_meta_title'] = $history_title;
        $row['current_meta_description'] = $history_description;
        $row['applied_meta_title'] = $history_title;
        $row['applied_meta_description'] = $history_description;
        $row['metadata_readback'] = $readback;
        $row['history_id'] = intval($history['id'] ?? 0);
        $row['history_applied'] = true;
        return $row;
    }

    private function aysa_agent8_onpage_provider_output_state_counts($rows) {
        $states = array(
            'actionable_review',
            'excluded_with_reason',
            'duplicate_with_reference',
            'invalid_with_reason',
            'already_optimized',
            'failed_with_reason',
        );
        $counts = array_fill_keys($states, 0);
        foreach (is_array($rows) ? $rows : array() as $row) {
            $state = sanitize_key((string) ($row['provider_output_state'] ?? 'invalid_with_reason'));
            if (!isset($counts[$state])) {
                $state = 'invalid_with_reason';
            }
            $counts[$state]++;
        }
        return $counts;
    }

    private function aysa_agent8_hydrate_onpage_business_relevance_row($row, $context) {
        if (!is_array($row)) {
            return array();
        }

        $classification = $this->aysa_agent8_classify_onpage_business_relevance($row, $context);
        $row['business_relevance'] = (string) ($classification['classification'] ?? 'needs_manual_review');
        $row['business_relevance_reason'] = (string) ($classification['reason'] ?? 'Relevanța pentru business trebuie verificată manual.');
        $row['business_relevance_score'] = floatval($classification['score'] ?? 0);
        $row['business_relevance_suggested_action'] = (string) ($classification['suggested_action'] ?? 'review_manually');
        $row['business_relevance_safe_eligible'] = $row['business_relevance'] === 'eligible_business_page';
        $row['business_relevance_evidence'] = isset($classification['evidence']) && is_array($classification['evidence'])
            ? $classification['evidence']
            : array();

        return $row;
    }

    private function aysa_agent8_classify_onpage_business_relevance($row, $context) {
        $row = is_array($row) ? $row : array();
        $context = is_array($context) ? $context : array();
        $entity_group = sanitize_key((string) ($row['entity_group'] ?? $row['post_type'] ?? ''));
        $object_kind = sanitize_key((string) ($row['object_kind'] ?? 'post'));
        $post_type = sanitize_key((string) ($row['post_type'] ?? ''));
        $publication_state = sanitize_key((string) ($row['publication_state'] ?? $row['post_status'] ?? $row['status'] ?? ''));
        $url = esc_url_raw((string) ($row['url'] ?? $row['permalink'] ?? $row['page_url'] ?? ''));
        $path = strtolower((string) wp_parse_url($url, PHP_URL_PATH));

        $system_post_types = array('attachment', 'revision', 'nav_menu_item', 'wp_block', 'wp_template', 'wp_template_part');
        $system_paths = array('/wp-admin', '/wp-json', '/feed', '/search', '/cart', '/checkout', '/my-account', '/contul-meu');
        $is_system = in_array($post_type, $system_post_types, true);
        foreach ($system_paths as $system_path) {
            if ($path !== '' && str_contains($path, $system_path)) {
                $is_system = true;
                break;
            }
        }
        if ($is_system || ($object_kind === 'term' && !in_array($entity_group, array('ecommerce_category', 'product_cat', 'category'), true))) {
            return array(
                'classification' => 'technical_or_system_page',
                'reason' => 'Pagina este tehnică sau de sistem și nu intră în optimizarea metadata de business.',
                'score' => 0,
                'suggested_action' => 'report_only',
                'evidence' => array('post_type' => $post_type, 'path' => $path),
            );
        }

        $title = trim((string) ($row['title'] ?? $row['page_title'] ?? $row['name'] ?? ''));
        $keyword = trim((string) ($row['primary_keyword'] ?? $row['main_keyword'] ?? $row['keyword'] ?? ''));
        $excerpt = trim(wp_strip_all_tags((string) ($row['content_excerpt'] ?? $row['content_snippet'] ?? $row['excerpt'] ?? '')));
        $category_text = '';
        $entity_id = intval($row['entity_id'] ?? $row['post_id'] ?? 0);
        if ($entity_id > 0 && ($entity_group === 'product' || $post_type === 'product')) {
            $terms = wp_get_post_terms($entity_id, 'product_cat', array('fields' => 'names'));
            if (!is_wp_error($terms) && is_array($terms)) {
                $category_text = implode(' ', array_map('strval', $terms));
            }
        }
        $evidence_text = trim(implode(' ', array_filter(array($keyword, $title, $path, $category_text, $excerpt))));
        $tokens = $this->aysa_adjustments_tokenize_keyword_text($evidence_text);
        $candidate = $this->aysa_adjustments_match_keyword_to_wp_item($evidence_text, $context['catalog_items'] ?? array());
        $score_row = $row;
        $score_row['source_keys'] = isset($score_row['source_keys']) && is_array($score_row['source_keys']) ? $score_row['source_keys'] : array();
        $score = $this->aysa_adjustments_score_keyword_relevance($evidence_text, $score_row, $context, $candidate);
        $passes_gate = $this->aysa_adjustments_keyword_passes_business_gate($evidence_text, $score_row, $context, $candidate);
        $business_tokens = array_values(array_diff(
            is_array($context['business_tokens'] ?? null) ? $context['business_tokens'] : array(),
            $this->aysa_adjustments_get_weak_context_tokens()
        ));
        $positive_tokens = array_values(array_intersect($tokens, $business_tokens));
        $profile_signal_tokens = array_values(array_unique(array_merge(
            $business_tokens,
            is_array($context['own_brand_tokens'] ?? null) ? $context['own_brand_tokens'] : array(),
            $this->aysa_adjustments_tokenize_keyword_text(implode(' ', is_array($context['seed_keywords'] ?? null) ? $context['seed_keywords'] : array()))
        )));
        $is_floral_business = false;
        foreach ($profile_signal_tokens as $profile_token) {
            if (preg_match('/flor|buchet|trandafir|bujor|lalea|orhide/', (string) $profile_token) === 1) {
                $is_floral_business = true;
                break;
            }
        }
        $industry_exclusions = is_array($context['industry_exclusions'] ?? null) ? $context['industry_exclusions'] : array();
        if ($is_floral_business) {
            $industry_exclusions = array_merge($industry_exclusions, array('yamato', 'btu', 'inverter', 'aer conditionat', 'boneta', 'satin'));
        }
        $exclusions = $this->aysa_adjustments_tokenize_keyword_text(implode(' ', $industry_exclusions));
        $excluded_tokens = array_values(array_intersect($tokens, $exclusions));
        $candidate_score = is_array($candidate) ? floatval($candidate['score'] ?? 0) : 0;

        if ($excluded_tokens !== array() && $positive_tokens === array()) {
            return array(
                'classification' => 'likely_irrelevant_legacy_or_test',
                'reason' => 'Subiectul paginii nu corespunde profilului business și pare conținut vechi, importat sau de test.',
                'score' => $score,
                'suggested_action' => 'consider_cleanup',
                'evidence' => array('excluded_tokens' => $excluded_tokens, 'title' => $title, 'url' => $url),
            );
        }

        if (($passes_gate && $score >= 0.28) || $candidate_score >= 0.42 || (count($positive_tokens) >= 1 && $score >= 0.34)) {
            return array(
                'classification' => 'eligible_business_page',
                'reason' => 'Pagina corespunde serviciilor, produselor sau temelor comerciale ale businessului.',
                'score' => $score,
                'suggested_action' => 'keep_eligible',
                'evidence' => array('positive_tokens' => $positive_tokens, 'catalog_match' => $candidate),
            );
        }

        if ($positive_tokens !== array() || $candidate_score > 0 || $score >= 0.18) {
            return array(
                'classification' => 'questionable_relevance',
                'reason' => 'Pagina are unele semnale relevante, dar legătura cu oferta businessului nu este suficient de clară.',
                'score' => $score,
                'suggested_action' => 'review_manually',
                'evidence' => array('positive_tokens' => $positive_tokens, 'catalog_match' => $candidate),
            );
        }

        return array(
            'classification' => 'needs_manual_review',
            'reason' => $publication_state !== '' && $publication_state !== 'publish'
                ? 'Pagina nu este publicată sau nu are suficiente date pentru o decizie sigură.'
                : 'Nu există suficiente dovezi pentru a confirma relevanța paginii pentru business.',
            'score' => $score,
            'suggested_action' => 'review_manually',
            'evidence' => array('title' => $title, 'url' => $url),
        );
    }

    private function aysa_agent8_hydrate_onpage_metadata_route_evidence_row($row) {
        if (!is_array($row)) {
            return array();
        }

        $entity_id = intval($row['entity_id'] ?? 0);
        if ($entity_id <= 0 || !function_exists('aysa_seo_backend_adapter')) {
            $row['metadata_route_evidence_available'] = false;
            $row['metadata_old_value_captured'] = false;
            return $row;
        }

        $object_kind = sanitize_key((string) ($row['object_kind'] ?? 'post')) === 'term' ? 'term' : 'post';
        $provider_meta = aysa_seo_backend_adapter()->get_metadata($entity_id, $object_kind);
        if (!is_array($provider_meta) || sanitize_key((string) ($provider_meta['backend'] ?? '')) === '') {
            $row['metadata_route_evidence_available'] = false;
            $row['metadata_old_value_captured'] = false;
            return $row;
        }

        $row['metadata_provider'] = sanitize_key((string) $provider_meta['backend']);
        $row['metadata_provider_mode'] = sanitize_key((string) ($provider_meta['mode'] ?? ''));
        $row['current_meta_title'] = trim((string) ($provider_meta['meta_title'] ?? ''));
        $row['current_meta_description'] = trim((string) ($provider_meta['meta_description'] ?? ''));
        $row['metadata_route_evidence_available'] = true;
        $row['metadata_old_value_captured'] = true;

        return $row;
    }

    private function aysa_agent8_sync_keyword_page_map_snapshot($project_id, $reason = '') {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array('ok' => false, 'status' => 'skipped', 'reason' => 'invalid_project_id');
        }

        $snapshot = $this->aysa_agent8_build_keyword_page_map_snapshot($project_id);
        $snapshot_hash = (string) ($snapshot['source_hash'] ?? '');
        if ($snapshot_hash === '') {
            return array('ok' => false, 'status' => 'skipped', 'reason' => 'empty_snapshot');
        }

        $option_key = 'aysa_keyword_page_map_snapshot_hash_' . $project_id;
        if ((string) get_option($option_key, '') === $snapshot_hash) {
            return array(
                'ok' => true,
                'status' => 'unchanged',
                'snapshot_hash' => $snapshot_hash,
                'summary' => $snapshot['summary'] ?? array(),
            );
        }

        $response = $this->aysa_agent8v3_api_request('POST', '/v1/projects/keyword-page-map/sync', array(
            'project_id' => $project_id,
            'reason' => sanitize_key((string) $reason),
            'snapshot' => $snapshot,
        ));
        if (is_wp_error($response)) {
            return array(
                'ok' => false,
                'status' => 'failed',
                'error' => $response->get_error_code(),
                'message' => $response->get_error_message(),
                'snapshot_hash' => $snapshot_hash,
                'summary' => $snapshot['summary'] ?? array(),
            );
        }
        if (!is_array($response)) {
            return array('ok' => false, 'status' => 'failed', 'reason' => 'invalid_api_response');
        }
        if (!empty($response['ok'])) {
            update_option($option_key, $snapshot_hash, false);
        }

        return $response;
    }

    private function aysa_agent8_build_keyword_page_map_snapshot($project_id) {
        $project_id = intval($project_id);
        $pages_by_key = array();
        $page = 1;
        $max_pages = 100;
        do {
            $workspace = $this->aysa_agent8_get_onpage_workspace_snapshot($project_id, array('tab' => 'keyword_mapping'), $page, 100);
            $rows = isset($workspace['rows']) && is_array($workspace['rows']) ? $workspace['rows'] : array();
            foreach ($rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $primary_keyword = trim((string) ($row['primary_keyword'] ?? $row['main_keyword'] ?? $row['keyword'] ?? ''));
                $keywords = $this->aysa_agent8_keyword_page_map_keywords_from_row($row, $primary_keyword, 'wordpress_onpage_workspace');
                if ($keywords === array() && $primary_keyword === '') {
                    continue;
                }
                $this->aysa_agent8_add_keyword_page_map_page($pages_by_key, array(
                    'url' => $this->aysa_agent8_keyword_page_map_canonical_target_url(
                        (string) ($row['url'] ?? ''),
                        (string) ($row['entity_group'] ?? $row['object_kind'] ?? $row['post_type'] ?? ''),
                        intval($row['entity_id'] ?? $row['id_intern'] ?? 0)
                    ),
                    'label' => sanitize_text_field((string) ($row['title'] ?? $row['label'] ?? $row['name'] ?? '')),
                    'associated_on' => sanitize_key((string) ($row['entity_group'] ?? $row['object_kind'] ?? $row['post_type'] ?? '')),
                    'id_intern' => intval($row['entity_id'] ?? $row['id_intern'] ?? 0),
                    'entity_key' => sanitize_text_field((string) ($row['entity_key'] ?? '')),
                    'primary_keyword' => $primary_keyword,
                    'keywords' => $keywords,
                    'mapping_state' => sanitize_key((string) ($row['mapping_state'] ?? $row['workflow_status'] ?? 'needs_mapping')),
                    'publication_state' => sanitize_key((string) ($row['publication_state'] ?? '')),
                    'source' => 'wordpress_onpage_workspace',
                ));
            }
            $pagination = isset($workspace['pagination']) && is_array($workspace['pagination']) ? $workspace['pagination'] : array();
            $total_pages = max(1, intval($pagination['pages'] ?? 1));
            $page++;
        } while ($page <= $total_pages && $page <= $max_pages);

        foreach ($this->get_keyword_discovery_local_associations($project_id) as $association) {
            if (!is_array($association)) {
                continue;
            }
            $keyword = trim((string) ($association['keyword'] ?? ''));
            if ($keyword === '') {
                continue;
            }
            $secondary_keywords = isset($association['secondaryKeywords']) && is_array($association['secondaryKeywords'])
                ? array_values(array_filter(array_map('strval', $association['secondaryKeywords'])))
                : array();
            $keywords = array(array(
                'keyword' => $keyword,
                'is_main' => !empty($association['isMain']),
                'association_id' => intval($association['id'] ?? 0),
                'source' => 'local_associations',
            ));
            foreach ($secondary_keywords as $secondary_keyword) {
                $secondary_keyword = trim((string) $secondary_keyword);
                if ($secondary_keyword === '') {
                    continue;
                }
                $keywords[] = array(
                    'keyword' => $secondary_keyword,
                    'is_main' => false,
                    'association_id' => intval($association['id'] ?? 0),
                    'source' => 'local_associations',
                );
            }
            $this->aysa_agent8_add_keyword_page_map_page($pages_by_key, array(
                'url' => $this->aysa_agent8_keyword_page_map_canonical_target_url(
                    (string) ($association['targetUrl'] ?? ''),
                    (string) ($association['associatedOn'] ?? ''),
                    intval($association['idIntern'] ?? 0)
                ),
                'label' => sanitize_text_field((string) ($association['value'] ?? '')),
                'associated_on' => sanitize_key((string) ($association['associatedOn'] ?? '')),
                'id_intern' => intval($association['idIntern'] ?? 0),
                'entity_key' => sanitize_text_field((string) ($association['entityKey'] ?? '')),
                'primary_keyword' => !empty($association['isMain']) ? $keyword : '',
                'keywords' => $keywords,
                'mapping_state' => 'mapped',
                'publication_state' => $this->aysa_agent8_keyword_page_map_publication_state(
                    (string) ($association['associatedOn'] ?? ''),
                    intval($association['idIntern'] ?? 0)
                ),
                'source' => 'local_associations',
            ));
        }

        $pages = array_values($pages_by_key);
        usort($pages, static function ($a, $b) {
            return strcmp((string) ($a['url'] ?? $a['entity_key'] ?? ''), (string) ($b['url'] ?? $b['entity_key'] ?? ''));
        });
        $summary = $this->aysa_agent8_keyword_page_map_summary($pages);
        $hash_payload = array(
            'project_id' => $project_id,
            'website' => home_url('/'),
            'summary' => $summary,
            'pages' => $pages,
        );
        $encoded = wp_json_encode($hash_payload);
        $source_hash = is_string($encoded) ? hash('sha256', $encoded) : '';

        $site_host = wp_parse_url(home_url('/'), PHP_URL_HOST);
        $site_host = is_string($site_host) ? strtolower($site_host) : '';

        return array(
            'project_id' => $project_id,
            'website' => home_url('/'),
            'site_host' => $site_host,
            'domain' => $site_host,
            'generated_at' => current_time('mysql', true),
            'source' => 'wordpress_onpage_workspace',
            'source_hash' => $source_hash,
            'summary' => $summary,
            'pages' => $pages,
        );
    }

    private function aysa_agent8_keyword_page_map_canonical_target_url($raw_url, $associated_on, $entity_id) {
        $raw_url = esc_url_raw((string) $raw_url);
        $associated_on = sanitize_key((string) $associated_on);
        $entity_id = intval($entity_id);
        if ($entity_id <= 0) {
            return $raw_url;
        }

        $is_term = in_array($associated_on, array('term', 'category', 'tag', 'taxonomy', 'ecommerce_category', 'product_category'), true);
        if ($is_term) {
            $term = get_term($entity_id);
            if ($term instanceof WP_Term
                && (!function_exists('is_taxonomy_viewable') || is_taxonomy_viewable($term->taxonomy))) {
                $term_url = get_term_link($term);
                if (!is_wp_error($term_url) && is_string($term_url)) {
                    return esc_url_raw($term_url);
                }
            }
            return $raw_url;
        }

        $post = get_post($entity_id);
        if ($post instanceof WP_Post
            && (string) $post->post_status === 'publish'
            && (!function_exists('is_post_type_viewable') || is_post_type_viewable($post->post_type))) {
            $permalink = get_permalink($post);
            if (is_string($permalink) && $permalink !== '') {
                return esc_url_raw($permalink);
            }
        }

        return $raw_url;
    }

    private function aysa_agent8_keyword_page_map_publication_state($associated_on, $entity_id) {
        $associated_on = sanitize_key((string) $associated_on);
        $entity_id = intval($entity_id);
        if ($entity_id <= 0) {
            return '';
        }

        if (in_array($associated_on, array('term', 'category', 'tag', 'taxonomy', 'ecommerce_category', 'product_category'), true)) {
            $term = get_term($entity_id);
            return $term instanceof WP_Term ? 'published' : 'missing';
        }

        $post = get_post($entity_id);
        return $post instanceof WP_Post ? sanitize_key((string) $post->post_status) : 'missing';
    }

    private function aysa_agent8_keyword_page_map_keywords_from_row(array $row, $primary_keyword, $source) {
        $keywords = array();
        $seen = array();
        $add_keyword = static function ($keyword, $is_main) use (&$keywords, &$seen, $source) {
            $keyword = trim((string) $keyword);
            if ($keyword === '') {
                return;
            }
            $key = strtolower($keyword);
            if (isset($seen[$key])) {
                return;
            }
            $seen[$key] = true;
            $keywords[] = array(
                'keyword' => $keyword,
                'is_main' => (bool) $is_main,
                'source' => $source,
            );
        };

        $add_keyword($primary_keyword, true);
        foreach (array('keywords', 'secondary_keywords', 'keyword_family') as $field) {
            $values = isset($row[$field]) && is_array($row[$field]) ? $row[$field] : array();
            foreach ($values as $value) {
                if (is_array($value)) {
                    $add_keyword($value['keyword'] ?? $value['value'] ?? '', !empty($value['is_main']) || !empty($value['primary']));
                } else {
                    $add_keyword($value, false);
                }
            }
        }

        return $keywords;
    }

    private function aysa_agent8_add_keyword_page_map_page(array &$pages_by_key, array $page) {
        $page = array_merge(
            $page,
            $this->aysa_agent8_keyword_page_map_target_evidence(
                (string) ($page['associated_on'] ?? ''),
                intval($page['id_intern'] ?? 0),
                (string) ($page['url'] ?? '')
            )
        );
        $url = esc_url_raw((string) ($page['url'] ?? ''));
        $entity_key = sanitize_text_field((string) ($page['entity_key'] ?? ''));
        $key = $entity_key !== '' ? 'entity:' . $entity_key : 'url:' . untrailingslashit($url);
        if ($key === 'url:') {
            $key = 'local:' . sanitize_key((string) ($page['associated_on'] ?? '')) . ':' . intval($page['id_intern'] ?? 0);
        }
        if (!isset($pages_by_key[$key])) {
            $pages_by_key[$key] = array(
                'url' => $url,
                'label' => sanitize_text_field((string) ($page['label'] ?? '')),
                'associated_on' => sanitize_key((string) ($page['associated_on'] ?? '')),
                'id_intern' => intval($page['id_intern'] ?? 0),
                'entity_key' => $entity_key,
                'primary_keyword' => trim((string) ($page['primary_keyword'] ?? '')),
                'keywords' => array(),
                'mapping_state' => sanitize_key((string) ($page['mapping_state'] ?? 'needs_mapping')),
                'publication_state' => sanitize_key((string) ($page['publication_state'] ?? '')),
                'canonical_url' => esc_url_raw((string) ($page['canonical_url'] ?? '')),
                'http_status' => isset($page['http_status']) ? intval($page['http_status']) : null,
                'http_status_source' => sanitize_key((string) ($page['http_status_source'] ?? '')),
                'noindex' => isset($page['noindex']) ? (bool) $page['noindex'] : null,
                'source' => sanitize_key((string) ($page['source'] ?? 'wordpress_onpage_workspace')),
            );
        }

        if ($pages_by_key[$key]['url'] === '' && $url !== '') {
            $pages_by_key[$key]['url'] = $url;
        }
        if ($pages_by_key[$key]['label'] === '' && trim((string) ($page['label'] ?? '')) !== '') {
            $pages_by_key[$key]['label'] = sanitize_text_field((string) $page['label']);
        }
        if ($pages_by_key[$key]['primary_keyword'] === '' && trim((string) ($page['primary_keyword'] ?? '')) !== '') {
            $pages_by_key[$key]['primary_keyword'] = trim((string) $page['primary_keyword']);
        }
        if (($pages_by_key[$key]['publication_state'] ?? '') === '' && trim((string) ($page['publication_state'] ?? '')) !== '') {
            $pages_by_key[$key]['publication_state'] = sanitize_key((string) $page['publication_state']);
        }
        foreach (array('canonical_url', 'http_status', 'http_status_source', 'noindex') as $evidence_field) {
            $current = $pages_by_key[$key][$evidence_field] ?? null;
            $candidate = $page[$evidence_field] ?? null;
            if (($current === null || $current === '') && $candidate !== null && $candidate !== '') {
                $pages_by_key[$key][$evidence_field] = $evidence_field === 'canonical_url'
                    ? esc_url_raw((string) $candidate)
                    : ($evidence_field === 'http_status'
                        ? intval($candidate)
                        : ($evidence_field === 'noindex'
                            ? (bool) $candidate
                            : sanitize_key((string) $candidate)));
            }
        }

        $seen = array();
        foreach ($pages_by_key[$key]['keywords'] as $existing) {
            if (is_array($existing)) {
                $seen[strtolower(trim((string) ($existing['keyword'] ?? '')))] = true;
            }
        }
        foreach (($page['keywords'] ?? array()) as $keyword_row) {
            if (!is_array($keyword_row)) {
                continue;
            }
            $keyword = trim((string) ($keyword_row['keyword'] ?? ''));
            if ($keyword === '') {
                continue;
            }
            $keyword_key = strtolower($keyword);
            if (isset($seen[$keyword_key])) {
                continue;
            }
            $seen[$keyword_key] = true;
            $pages_by_key[$key]['keywords'][] = array_filter(array(
                'keyword' => $keyword,
                'is_main' => !empty($keyword_row['is_main']),
                'association_id' => isset($keyword_row['association_id']) ? intval($keyword_row['association_id']) : null,
                'source' => sanitize_key((string) ($keyword_row['source'] ?? $page['source'] ?? 'wordpress_onpage_workspace')),
            ), static function ($value) {
                return $value !== null && $value !== '';
            });
        }
    }

    private function aysa_agent8_keyword_page_map_target_evidence($associated_on, $entity_id, $raw_url = '') {
        $associated_on = sanitize_key((string) $associated_on);
        $entity_id = intval($entity_id);
        $raw_url = esc_url_raw((string) $raw_url);
        $evidence = array(
            'canonical_url' => '',
            'http_status' => null,
            'http_status_source' => '',
            'noindex' => null,
        );
        if ($entity_id <= 0) {
            return $evidence;
        }

        $is_term = in_array($associated_on, array('term', 'category', 'tag', 'taxonomy', 'ecommerce_category', 'product_category', 'product_cat'), true);
        if ($is_term) {
            $term = get_term($entity_id);
            if (!($term instanceof WP_Term)
                || (function_exists('is_taxonomy_viewable') && !is_taxonomy_viewable($term->taxonomy))) {
                return array_merge($evidence, array('publication_state' => 'missing'));
            }
            $term_url = get_term_link($term);
            if (is_wp_error($term_url) || !is_string($term_url) || trim($term_url) === '') {
                return array_merge($evidence, array('publication_state' => 'missing'));
            }
            $term_url = esc_url_raw($term_url);
            return array_merge($evidence, array(
                'url' => $term_url,
                'publication_state' => 'published',
                'canonical_url' => $term_url,
                'http_status' => 200,
                'http_status_source' => 'wordpress_published_permalink',
                'noindex' => $this->aysa_technical_term_is_noindex($entity_id),
            ));
        }

        $post = get_post($entity_id);
        if (!($post instanceof WP_Post)) {
            return array_merge($evidence, array('publication_state' => 'missing'));
        }
        $publication_state = sanitize_key((string) $post->post_status);
        $viewable = !function_exists('is_post_type_viewable') || is_post_type_viewable($post->post_type);
        $permalink = get_permalink($post);
        $permalink = is_string($permalink) ? esc_url_raw($permalink) : '';
        if ($publication_state !== 'publish' || !$viewable || $permalink === '') {
            return array_merge($evidence, array(
                'publication_state' => $publication_state !== '' ? $publication_state : 'missing',
                'noindex' => $this->aysa_technical_post_is_noindex($entity_id),
            ));
        }

        $explicit_canonical = $this->aysa_technical_site_audit_current_post_canonical_url($entity_id);
        return array_merge($evidence, array(
            'url' => $permalink,
            'publication_state' => 'published',
            'canonical_url' => $explicit_canonical !== '' ? $explicit_canonical : $permalink,
            'http_status' => 200,
            'http_status_source' => 'wordpress_published_permalink',
            'noindex' => $this->aysa_technical_post_is_noindex($entity_id),
        ));
    }

    private function aysa_agent8_keyword_page_map_summary(array $pages) {
        $keywords = array();
        $associations = 0;
        foreach ($pages as $page) {
            $rows = isset($page['keywords']) && is_array($page['keywords']) ? $page['keywords'] : array();
            foreach ($rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $keyword = strtolower(trim((string) ($row['keyword'] ?? '')));
                if ($keyword === '') {
                    continue;
                }
                $keywords[$keyword] = true;
                $associations++;
            }
        }

        return array(
            'pages_count' => count($pages),
            'keywords_count' => count($keywords),
            'associations_count' => $associations,
        );
    }

    private function aysa_agent8_get_onpage_aeo_counts($project_id, $filters = array()) {
        $project_id = intval($project_id);
        $service = $this->aysa_onpage_workspace_service();
        if ($project_id <= 0 || !$service || !method_exists($service, 'build_run_selection_meta')) {
            return array();
        }

        $filters = is_array($filters) ? $filters : array();
        $entity_filter = sanitize_key((string) ($filters['entity_type'] ?? 'all'));
        $entity_group_map = array(
            'onpage_entity_product' => 'product',
            'entity_product' => 'product',
            'onpage_entity_ecommerce_category' => 'ecommerce_category',
            'entity_category' => 'ecommerce_category',
            'onpage_entity_page' => 'page',
            'entity_page' => 'page',
            'onpage_entity_article' => 'article',
            'entity_article' => 'article',
            'product' => 'product',
            'ecommerce_category' => 'ecommerce_category',
            'page' => 'page',
            'article' => 'article',
        );
        $allowed_groups = isset($entity_group_map[$entity_filter])
            ? array($entity_group_map[$entity_filter])
            : array('product', 'ecommerce_category', 'page', 'article');

        $counts = array(
            'ready' => 0,
            'needs_generation' => 0,
            'needs_summary' => 0,
            'faq_opportunity' => 0,
            'trust_missing' => 0,
            'can_apply' => 0,
            'applied' => 0,
        );
        $contexts = $service->build_run_selection_meta($project_id, 'all', array(), array());
        foreach ($contexts as $context) {
            if (!is_array($context)) {
                continue;
            }
            $entity_group = sanitize_key((string) ($context['entity_group'] ?? $context['post_type'] ?? $context['taxonomy'] ?? ''));
            if (!in_array($entity_group, $allowed_groups, true)) {
                continue;
            }
            $package = $this->aysa_agent8_decode_onpage_package_value($context['rewrite_package_json'] ?? array());
            $is_aeo = sanitize_key((string) ($package['generation_focus'] ?? '')) === 'aeo_content_qa'
                || sanitize_key((string) ($context['optimization_scope'] ?? '')) === 'aeo_content_qa';
            $has_summary = trim((string) ($package['aeo_summary_html'] ?? '')) !== ''
                || ($is_aeo && trim((string) ($package['seo_appendix_html'] ?? '')) !== '');
            $has_faq = trim((string) ($package['faq_html'] ?? '')) !== '';
            $findings = isset($package['aeo_findings']) && is_array($package['aeo_findings']) ? $package['aeo_findings'] : array();
            $trust_missing = false;
            foreach ($findings as $finding) {
                $lower = strtolower(trim((string) $finding));
                if (strpos($lower, 'trust') !== false || strpos($lower, 'context') !== false || strpos($lower, 'author') !== false || strpos($lower, 'source') !== false) {
                    $trust_missing = true;
                    break;
                }
            }
            if ($this->aysa_agent8_is_onpage_aeo_generation_candidate($context)) {
                $counts['needs_generation']++;
            }
            if (!$has_summary) {
                $counts['needs_summary']++;
            }
            if (!$has_faq) {
                $counts['faq_opportunity']++;
            }
            if ($trust_missing) {
                $counts['trust_missing']++;
            }
            if ($is_aeo && $has_summary && $has_faq && $findings === array()) {
                $counts['ready']++;
            }
            if ($this->aysa_agent8_is_onpage_aeo_apply_candidate($context)) {
                $counts['can_apply']++;
            }
            $entity_id = intval($context['entity_id'] ?? 0);
            $object_kind = strtolower(trim((string) ($context['object_kind'] ?? 'post')));
            if ($entity_id > 0 && $object_kind !== 'term' && trim((string) get_post_meta($entity_id, '_aysa_seo_appendix_html', true)) !== '') {
                $counts['applied']++;
            }
        }

        return $counts;
    }

    private function aysa_agent8_get_onpage_mapping_counts($project_id, $filters = array()) {
        $service = $this->aysa_onpage_workspace_service();
        if (!$service) {
            return array();
        }

        $project_id = intval($project_id);
        $filters = is_array($filters) ? $filters : array();
        $entity_filter = sanitize_key((string) ($filters['entity_type'] ?? 'all'));
        $entity_group_map = array(
            'onpage_entity_product' => 'product',
            'entity_product' => 'product',
            'onpage_entity_ecommerce_category' => 'ecommerce_category',
            'entity_category' => 'ecommerce_category',
            'onpage_entity_page' => 'page',
            'entity_page' => 'page',
            'onpage_entity_article' => 'article',
            'entity_article' => 'article',
            'product' => 'product',
            'ecommerce_category' => 'ecommerce_category',
            'page' => 'page',
            'article' => 'article',
        );
        $entity_groups = isset($entity_group_map[$entity_filter])
            ? array($entity_group_map[$entity_filter])
            : array('product', 'ecommerce_category', 'page', 'article');

        $base_filters = array();
        $search = trim((string) ($filters['search'] ?? $filters['q'] ?? ''));
        if ($search !== '') {
            $base_filters['q'] = $search;
        }

        $counts = array(
            'entities' => 0,
            'mapped' => 0,
            'ai_suggested' => 0,
            'mapping_review' => 0,
            'mapping_required' => 0,
            'mapping_ignored' => 0,
        );
        foreach ($entity_groups as $entity_group) {
            $page = 1;
            do {
                $workspace = $service->build_workspace($project_id, array(
                    'entity_group' => $entity_group,
                    'filters' => $base_filters,
                    'page' => $page,
                    'per_page' => 100,
                    'refresh' => false,
                ));
                $rows = isset($workspace['rows']) && is_array($workspace['rows']) ? $workspace['rows'] : array();
                foreach ($rows as $row) {
                    if (!is_array($row) || sanitize_key((string) ($row['publication_state'] ?? '')) !== 'published') {
                        continue;
                    }
                    $counts['entities']++;
                    $state = sanitize_key((string) ($row['mapping_state'] ?? 'needs_mapping'));
                    if ($state === 'mapped') {
                        $counts['mapped']++;
                    } elseif ($state === 'ai_suggested') {
                        $counts['ai_suggested']++;
                    } elseif ($state === 'mapping_review') {
                        $counts['mapping_review']++;
                    } elseif ($state === 'ignored' || $state === 'mapping_ignored') {
                        $counts['mapping_ignored']++;
                    } else {
                        $counts['mapping_required']++;
                    }
                }
                $pagination = isset($workspace['pagination']) && is_array($workspace['pagination']) ? $workspace['pagination'] : array();
                $pages = max(1, intval($pagination['pages'] ?? 1));
                $page++;
            } while ($page <= $pages);
        }

        return $counts;
    }

    private function aysa_agent8_hydrate_onpage_seo_appendix_row($row) {
        if (!is_array($row)) {
            return $row;
        }

        $entity_id = intval($row['entity_id'] ?? $row['id_intern'] ?? $row['post_id'] ?? 0);
        if ($entity_id <= 0 || strtolower(trim((string) ($row['object_kind'] ?? 'post'))) === 'term') {
            $row['seo_appendix_applied'] = false;
            return $row;
        }

        $html = trim((string) get_post_meta($entity_id, '_aysa_seo_appendix_html', true));
        $hash = trim((string) get_post_meta($entity_id, '_aysa_seo_appendix_content_hash', true));
        $applied_at = trim((string) get_post_meta($entity_id, '_aysa_seo_appendix_applied_at', true));
        $generated_at = trim((string) get_post_meta($entity_id, '_aysa_seo_appendix_generated_at', true));
        $row['seo_appendix_applied'] = $html !== '';
        $row['seo_appendix_applied_at'] = $applied_at !== '' ? $applied_at : $generated_at;
        $row['seo_appendix_hash'] = $hash !== '' ? $hash : ($html !== '' ? md5($html) : '');

        return $row;
    }

    private function aysa_agent8_hydrate_onpage_featured_alt_row($row) {
        if (!is_array($row)) {
            return $row;
        }

        $entity_id = intval($row['entity_id'] ?? $row['id_intern'] ?? $row['post_id'] ?? $row['term_id'] ?? 0);
        if ($entity_id <= 0) {
            return $row;
        }

        $object_kind = strtolower(trim((string) ($row['object_kind'] ?? 'post')));
        $taxonomy = (string) ($row['taxonomy'] ?? '');
        $attachment_id = 0;
        if ($object_kind === 'term' || $taxonomy === 'product_cat') {
            $attachment_id = intval(get_term_meta($entity_id, 'thumbnail_id', true));
        } else {
            $attachment_id = intval(get_post_thumbnail_id($entity_id));
        }

        $main_keyword = trim((string) ($row['main_keyword'] ?? $row['primary_keyword'] ?? ''));
        if ($attachment_id <= 0) {
            $row['featured_image_alt_target'] = array(
                'state' => 'not_available',
                'attachment_id' => 0,
                'image_url' => '',
                'current_alt' => '',
                'primary_keyword' => $main_keyword,
                'safe_to_generate' => false,
            );
            $row['featured_image_alt_state'] = 'not_available';
            return $row;
        }

        $image_url = wp_get_attachment_url($attachment_id);
        $current_alt = trim((string) get_post_meta($attachment_id, '_wp_attachment_image_alt', true));
        $generated_at = trim((string) get_post_meta($attachment_id, '_aysa_image_alt_generated_at', true));
        $source = trim((string) get_post_meta($attachment_id, '_aysa_image_alt_source', true));
        $state = $current_alt === '' ? 'missing' : ($generated_at !== '' ? 'aysa_generated' : 'present');

        $row['featured_image_alt_target'] = array(
            'state' => $state,
            'attachment_id' => $attachment_id,
            'image_url' => $image_url ? esc_url_raw((string) $image_url) : '',
            'image_filename' => $image_url ? sanitize_text_field(basename((string) wp_parse_url((string) $image_url, PHP_URL_PATH))) : '',
            'current_alt' => $current_alt,
            'primary_keyword' => $main_keyword,
            'source' => $source,
            'generated_at' => $generated_at,
            'safe_to_generate' => $state === 'missing',
        );
        $row['featured_image_alt_state'] = $state;

        return $row;
    }

    private function aysa_agent8_compact_onpage_modal_snapshot($snapshot, $tab) {
        if (!is_array($snapshot)) {
            return array();
        }

        $tab = sanitize_key((string) $tab);
        $rows = isset($snapshot['rows']) && is_array($snapshot['rows']) ? $snapshot['rows'] : array();
        $compact_rows = array();
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $compact_rows[] = $this->aysa_agent8_compact_onpage_modal_row($row, $tab);
        }

        return array(
            'summary' => isset($snapshot['summary']) && is_array($snapshot['summary']) ? $snapshot['summary'] : array(),
            'filters' => isset($snapshot['filters']) && is_array($snapshot['filters']) ? $snapshot['filters'] : array(),
            'rows' => $compact_rows,
            'pagination' => isset($snapshot['pagination']) && is_array($snapshot['pagination']) ? $snapshot['pagination'] : array(),
            'run' => isset($snapshot['run']) && is_array($snapshot['run']) ? $snapshot['run'] : array(),
            'active_entity_group' => isset($snapshot['active_entity_group']) ? (string) $snapshot['active_entity_group'] : 'all',
            'agent8_full_snapshot' => !empty($snapshot['agent8_full_snapshot']),
            'agent8_server_paginated' => !empty($snapshot['agent8_server_paginated']),
            'agent8_entity_group_counts' => isset($snapshot['agent8_entity_group_counts']) && is_array($snapshot['agent8_entity_group_counts']) ? $snapshot['agent8_entity_group_counts'] : array(),
            'agent8_mapping_counts' => isset($snapshot['agent8_mapping_counts']) && is_array($snapshot['agent8_mapping_counts']) ? $snapshot['agent8_mapping_counts'] : array(),
            'agent8_aeo_counts' => isset($snapshot['agent8_aeo_counts']) && is_array($snapshot['agent8_aeo_counts']) ? $snapshot['agent8_aeo_counts'] : array(),
        );
    }

    private function aysa_agent8_compact_onpage_modal_row($row, $tab) {
        $tab = sanitize_key((string) $tab);
        $copy_keys = array(
            'entity_key', 'entity_id', 'object_kind', 'post_type', 'taxonomy', 'url', 'permalink',
            'title', 'entity_group', 'publication_state', 'mapping_state', 'seo_state',
            'optimization_scope', 'meta_state', 'rewrite_state', 'output_state', 'main_keyword',
            'primary_keyword', 'keywords', 'secondary_keywords', 'confidence_score',
            'comparison_result', 'keyword_owner_entity_key', 'keyword_owner_url',
            'keyword_conflict_keyword', 'keyword_conflict_reason', 'decision_reason',
            'rejected_keyword', 'next_step', 'seo_score', 'content_source', 'worker_run_id', 'last_evaluated_at',
            'featured_image_alt_target', 'featured_image_alt_state',
            'seo_appendix_applied', 'seo_appendix_applied_at', 'seo_appendix_hash',
            'business_relevance', 'business_relevance_reason', 'business_relevance_score',
            'business_relevance_suggested_action', 'business_relevance_safe_eligible',
            'provider_output_id', 'provider_output_state', 'provider_output_reason_code',
            'provider_output_reason', 'duplicate_reference', 'source_run_id', 'stable_row_id', 'item_id',
            'history_id', 'history_applied',
        );
        if ($tab === 'meta_snippets') {
            $copy_keys[] = 'meta_package_json';
            $copy_keys[] = 'current_meta_title';
            $copy_keys[] = 'current_meta_description';
            $copy_keys[] = 'applied_meta_title';
            $copy_keys[] = 'applied_meta_description';
            $copy_keys[] = 'metadata_readback';
            $copy_keys[] = 'metadata_provider';
            $copy_keys[] = 'metadata_provider_mode';
            $copy_keys[] = 'metadata_route_evidence_available';
            $copy_keys[] = 'metadata_old_value_captured';
        } elseif ($tab === 'content' || $tab === 'aeo_content_qa') {
            $copy_keys[] = 'rewrite_decision_json';
            $copy_keys[] = 'rewrite_package_json';
            $copy_keys[] = 'content_status';
            $copy_keys[] = 'history_applied';
            $copy_keys[] = 'has_aysa_content_signal';
        } elseif ($tab === 'links_authority') {
            foreach (array('inbound_links', 'outbound_links', 'weak_orphan_status', 'planned_links', 'applied_links', 'link_opportunities', 'links_status') as $key) {
                $copy_keys[] = $key;
            }
        } elseif ($tab === 'image_alt') {
            $copy_keys[] = 'rewrite_package_json';
        } elseif ($tab === 'execution_report') {
            $copy_keys[] = 'status';
            $copy_keys[] = 'reason';
            $copy_keys[] = 'notes';
            $copy_keys[] = 'metadata_readback';
            $copy_keys[] = 'current_meta_title';
            $copy_keys[] = 'current_meta_description';
            $copy_keys[] = 'applied_meta_title';
            $copy_keys[] = 'applied_meta_description';
            $copy_keys[] = 'metadata_provider';
            $copy_keys[] = 'metadata_provider_mode';
            $copy_keys[] = 'metadata_route_evidence_available';
            $copy_keys[] = 'metadata_old_value_captured';
        }

        $out = array();
        foreach (array_values(array_unique($copy_keys)) as $key) {
            if (array_key_exists($key, $row)) {
                $out[$key] = $row[$key];
            }
        }

        if (isset($out['rewrite_decision_json'])) {
            $out['rewrite_decision_json'] = $this->aysa_agent8_compact_onpage_rewrite_decision($out['rewrite_decision_json']);
        }
        if (isset($out['rewrite_package_json'])) {
            $out['rewrite_package_json'] = $this->aysa_agent8_compact_onpage_rewrite_package($out['rewrite_package_json'], $tab);
        }
        if (isset($out['meta_package_json'])) {
            $out['meta_package_json'] = $this->aysa_agent8_compact_onpage_meta_package($out['meta_package_json']);
        }

        return $out;
    }

    private function aysa_agent8_compact_onpage_rewrite_decision($value) {
        $decision = $this->aysa_agent8_decode_onpage_package_value($value);
        $keys = array(
            'rewrite_needed', 'rewrite_mode', 'rewrite_reason', 'decision_reason', 'reason',
            'content_gaps', 'angle_gaps', 'recommended_sections', 'target_length_range',
            'final_length_strategy', 'benchmark_source', 'competitors_analyzed', 'risk_flags',
        );
        return $this->aysa_agent8_array_only_keys($decision, $keys);
    }

    private function aysa_agent8_compact_onpage_rewrite_package($value, $tab) {
        $package = $this->aysa_agent8_decode_onpage_package_value($value);
        if ($tab === 'image_alt') {
            return array(
                'featured_image_alt' => isset($package['featured_image_alt']) && is_array($package['featured_image_alt']) ? $package['featured_image_alt'] : array(),
            );
        }

        $keys = array(
            'generation_focus', 'content_title', 'content_description', 'rewritten_content', 'seo_appendix_html',
            'aeo_summary_html', 'faq_html', 'faq_schema_jsonld', 'aeo_findings', 'safe_to_auto_apply',
            'short_description_html', 'long_description_html', 'category_description_html',
            'content_surface', 'content_targets', 'apply_targets', 'featured_image_alt',
            'target_length_range', 'final_length_strategy', 'review_required', 'risk_flags',
            'benchmark_source', 'competitors_analyzed', 'confidence_score',
        );
        $out = $this->aysa_agent8_array_only_keys($package, $keys);
        foreach (array('rewritten_content', 'seo_appendix_html', 'aeo_summary_html', 'faq_html', 'faq_schema_jsonld', 'short_description_html', 'long_description_html', 'category_description_html') as $html_key) {
            if (isset($out[$html_key]) && is_string($out[$html_key]) && strlen($out[$html_key]) > 5000) {
                $out[$html_key] = mb_substr($out[$html_key], 0, 5000) . '...';
            }
        }
        return $out;
    }

    private function aysa_agent8_compact_onpage_meta_package($value) {
        return $this->aysa_agent8_array_only_keys($this->aysa_agent8_decode_onpage_package_value($value), array(
            'title', 'description', 'meta_title', 'meta_description', 'h1_suggestion',
            'confidence_score', 'reason', 'decision_reason',
        ));
    }

    private function aysa_agent8_decode_onpage_package_value($value) {
        if (is_array($value)) {
            return $value;
        }
        if (is_string($value) && trim($value) !== '') {
            $decoded = json_decode($value, true);
            if (is_array($decoded)) {
                return $decoded;
            }
        }
        return array();
    }

    private function aysa_agent8_array_only_keys($source, $keys) {
        $source = is_array($source) ? $source : array();
        $out = array();
        foreach ($keys as $key) {
            if (array_key_exists($key, $source)) {
                $out[$key] = $source[$key];
            }
        }
        return $out;
    }

    private function aysa_agent8_compact_onpage_keyword_mapping_context($payload) {
        if (!is_array($payload)) {
            return array();
        }
        if (isset($payload['keyword_mapping_workspace']) && is_array($payload['keyword_mapping_workspace'])) {
            $workspace = $payload['keyword_mapping_workspace'];
            $payload['keyword_mapping_workspace'] = array(
                'entity_coverage' => $this->aysa_agent8_compact_keyword_mapping_rows($workspace['entity_coverage'] ?? array(), 250),
                'clusters' => $this->aysa_agent8_compact_keyword_mapping_rows($workspace['clusters'] ?? array(), 250),
            );
        }
        if (isset($payload['local_keyword_associations']) && is_array($payload['local_keyword_associations'])) {
            $payload['local_keyword_associations'] = $this->aysa_agent8_compact_keyword_mapping_rows($payload['local_keyword_associations'], 400);
        }
        return $payload;
    }

    private function aysa_agent8_compact_keyword_mapping_rows($rows, $limit) {
        $rows = is_array($rows) ? array_values($rows) : array();
        $out = array();
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $out[] = $this->aysa_agent8_array_only_keys($row, array(
                'url', 'permalink', 'page_url', 'target_url', 'title', 'page_title', 'post_title',
                'targetUrl', 'associatedOn', 'associated_on', 'idIntern', 'id_intern', 'value',
                'entityKey', 'entity_key', 'mappingSource', 'mapping_source', 'source',
                'mapping_state', 'mappingState', 'review_status', 'reviewStatus',
                'name', 'entity_type', 'type', 'taxonomy', 'coverage_status', 'selection_state',
                'mapping_status', 'primary_keyword', 'primary_keyword_label', 'secondary_keywords',
                'keywords', 'member_keywords', 'label', 'matched_page', 'selected_page',
                'keyword', 'main_keyword',
            ));
            if (count($out) >= intval($limit)) {
                break;
            }
        }
        return $out;
    }

    private function aysa_agent8_canonicalize_onpage_row_url($row) {
        if (!is_array($row)) {
            return $row;
        }

        $url = trim((string) ($row['url'] ?? $row['permalink'] ?? $row['page_url'] ?? ''));
        if ($url === '') {
            return $row;
        }

        $path = trim((string) wp_parse_url($url, PHP_URL_PATH), '/');
        $query = (string) wp_parse_url($url, PHP_URL_QUERY);
        if ($query === '' || $path !== '') {
            return $row;
        }

        parse_str($query, $query_vars);
        $query_post_id = intval($query_vars['p'] ?? $query_vars['page_id'] ?? 0);
        $entity_id = intval($row['entity_id'] ?? $row['id_intern'] ?? $row['post_id'] ?? 0);
        $post_id = $query_post_id > 0 ? $query_post_id : $entity_id;
        if ($post_id > 0) {
            $permalink = get_permalink($post_id);
            if (is_string($permalink) && $permalink !== '') {
                $permalink_path = trim((string) wp_parse_url($permalink, PHP_URL_PATH), '/');
                $permalink_query = (string) wp_parse_url($permalink, PHP_URL_QUERY);
                if ($permalink_path === '' || $permalink_query !== '') {
                    $row['original_url'] = $url;
                    $row['mapping_state'] = 'ignored_not_indexable';
                    $row['indexability_status'] = 'not_indexable';
                    $row['publication_state'] = 'ignored_not_indexable';
                    $row['decision_reason'] = 'Query URL did not resolve to a clean canonical permalink; excluded from On Page SEO.';
                    return $row;
                }
                $row['original_url'] = $url;
                $row['url'] = $permalink;
                $row['permalink'] = $permalink;
                $row['page_url'] = $permalink;
                $row['canonical_url'] = $permalink;
                return $row;
            }
        }

        $row['original_url'] = $url;
        $row['mapping_state'] = 'ignored_not_indexable';
        $row['indexability_status'] = 'not_indexable';
        $row['publication_state'] = 'ignored_not_indexable';
        $row['decision_reason'] = 'Query URL without canonical permalink; excluded from On Page SEO.';

        return $row;
    }

    private function aysa_agent8_hydrate_onpage_applied_meta_row($row) {
        if (!is_array($row)) {
            return $row;
        }

        $meta_state = sanitize_key((string) ($row['meta_state'] ?? ''));
        $output_state = sanitize_key((string) ($row['output_state'] ?? ''));
        $is_applied = $meta_state === 'applied' || $output_state === 'meta_applied';
        if (!$is_applied) {
            return $row;
        }

        $entity_id = intval($row['entity_id'] ?? 0);
        if ($entity_id <= 0) {
            return $row;
        }

        $is_term = (string) ($row['object_kind'] ?? 'post') === 'term';
        $read_meta = static function($key) use ($entity_id, $is_term) {
            return trim((string) ($is_term ? get_term_meta($entity_id, $key, true) : get_post_meta($entity_id, $key, true)));
        };

        $title = trim((string) ($row['applied_meta_title'] ?? ''));
        if ($title === '') {
            $title = trim((string) ($row['current_meta_title'] ?? ''));
        }
        if ($title === '') {
            $title = $read_meta('_aysa_seo_title');
        }
        if ($title === '') {
            $title = $read_meta('_aysa_generated_meta_title');
        }

        $description = trim((string) ($row['applied_meta_description'] ?? ''));
        if ($description === '') {
            $description = trim((string) ($row['current_meta_description'] ?? ''));
        }
        if ($description === '') {
            $description = $read_meta('_aysa_seo_description');
        }
        if ($description === '') {
            $description = $read_meta('_aysa_generated_meta_description');
        }

        if ($title !== '') {
            $row['current_meta_title'] = trim((string) ($row['current_meta_title'] ?? '')) !== '' ? $row['current_meta_title'] : $title;
            $row['applied_meta_title'] = trim((string) ($row['applied_meta_title'] ?? '')) !== '' ? $row['applied_meta_title'] : $title;
        }
        if ($description !== '') {
            $row['current_meta_description'] = trim((string) ($row['current_meta_description'] ?? '')) !== '' ? $row['current_meta_description'] : $description;
            $row['applied_meta_description'] = trim((string) ($row['applied_meta_description'] ?? '')) !== '' ? $row['applied_meta_description'] : $description;
        }

        $package = $this->aysa_agent8_decode_onpage_package_value($row['meta_package_json'] ?? ($row['meta_package'] ?? array()));
        $expected_title = trim((string) ($package['meta_title'] ?? $package['title'] ?? $row['applied_meta_title'] ?? ''));
        $expected_description = trim((string) ($package['meta_description'] ?? $package['description'] ?? $row['applied_meta_description'] ?? ''));
        $provider_title = $read_meta('_aysa_seo_title');
        if ($provider_title === '') {
            $provider_title = $read_meta('_aysa_meta_title');
        }
        if ($provider_title === '') {
            $provider_title = $read_meta('_yoast_wpseo_title');
        }
        if ($provider_title === '') {
            $provider_title = $read_meta('rank_math_title');
        }
        $provider_description = $read_meta('_aysa_seo_description');
        if ($provider_description === '') {
            $provider_description = $read_meta('_aysa_meta_description');
        }
        if ($provider_description === '') {
            $provider_description = $read_meta('_yoast_wpseo_metadesc');
        }
        if ($provider_description === '') {
            $provider_description = $read_meta('_yoast_wpseo_desc');
        }
        if ($provider_description === '') {
            $provider_description = $read_meta('rank_math_description');
        }
        $title_status = $expected_title === '' ? 'not_expected' : ($provider_title === $expected_title ? 'matched' : 'drift');
        $description_status = $expected_description === '' ? 'not_expected' : ($provider_description === $expected_description ? 'matched' : 'drift');
        $drift_fields = array();
        if ($title_status === 'drift') {
            $drift_fields[] = 'title';
        }
        if ($description_status === 'drift') {
            $drift_fields[] = 'meta_description';
        }
        $readback_status = $drift_fields !== array() ? 'provider_drift_needs_confirmation' : 'matched';
        $row['metadata_readback'] = array(
            'status' => $readback_status,
            'expected' => array(
                'meta_title' => $expected_title,
                'meta_description' => $expected_description,
                'source' => 'meta_package',
            ),
            'live_provider' => array(
                'meta_title' => $provider_title,
                'meta_description' => $provider_description,
            ),
            'public_rendered' => array(
                'status' => 'not_checked',
                'title' => '',
                'meta_description' => '',
                'http_code' => 0,
            ),
            'field_status' => array(
                'title' => array(
                    'expected' => $expected_title,
                    'provider' => $provider_title,
                    'public' => '',
                    'provider_status' => $title_status,
                    'public_status' => 'not_checked',
                ),
                'meta_description' => array(
                    'expected' => $expected_description,
                    'provider' => $provider_description,
                    'public' => '',
                    'provider_status' => $description_status,
                    'public_status' => 'not_checked',
                ),
            ),
            'drift_fields' => $drift_fields,
            'client_message' => $readback_status === 'provider_drift_needs_confirmation'
                ? 'AYSA a găsit valori salvate diferit față de ce s-a aplicat anterior. Nu reaplic automat.'
                : '',
            'safe_next_action' => $readback_status === 'provider_drift_needs_confirmation'
                ? 'Reaplică doar rândul selectat, cu confirmare explicită.'
                : '',
            'billing_allowed' => false,
            'readback_only' => true,
        );
        if ($readback_status === 'provider_drift_needs_confirmation') {
            $row['meta_state'] = 'provider_drift_needs_confirmation';
            $row['output_state'] = 'provider_drift_needs_confirmation';
            $row['decision_reason'] = 'AYSA a găsit valori salvate diferit față de ce s-a aplicat anterior. Nu reaplic automat.';
            $row['next_step'] = 'Confirmă reaplicarea doar pentru acest rând.';
        }

        return $row;
    }

    private function aysa_agent8_prepare_onpage_meta_generation_payload($payload, $project_id) {
        $payload = is_array($payload) ? $payload : array();
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return $payload;
        }

        $incoming_batch_info = isset($payload['batch_info']) && is_array($payload['batch_info'])
            ? $payload['batch_info']
            : array();
        $worker_batch_size = 10;
        if (isset($incoming_batch_info['worker_batch_size'])) {
            $worker_batch_size = max(1, min(10, intval($incoming_batch_info['worker_batch_size'])));
        } elseif (isset($incoming_batch_info['batch_size'])) {
            $worker_batch_size = max(1, min(10, intval($incoming_batch_info['batch_size'])));
        } elseif (isset($payload['worker_batch_size'])) {
            $worker_batch_size = max(1, min(10, intval($payload['worker_batch_size'])));
        } elseif (isset($payload['batch_size'])) {
            $worker_batch_size = max(1, min(10, intval($payload['batch_size'])));
        }

        $selection = isset($payload['selection']) && is_array($payload['selection'])
            ? array_values(array_filter(array_map('strval', $payload['selection'])))
            : array();

        $selection = array_values(array_unique($selection));
        $generation_focus = sanitize_key((string) ($payload['generation_focus'] ?? ''));
        $section = sanitize_key((string) ($payload['section'] ?? ''));
        $selection_scope = sanitize_key((string) ($payload['selection_scope'] ?? ''));
        $content_confirmation_kind = sanitize_key((string) ($payload['confirmation_kind'] ?? ''));
        $is_content_generation = in_array($content_confirmation_kind, array('onpage_content_evaluation_generation', 'onpage_content_rewrite_generation'), true);
        $is_controlled_aeo_preview = in_array($selection_scope, array('single_confirmed_mapped_row', 'small_batch_confirmed_mapped_rows'), true)
            && sanitize_key((string) ($payload['confirmation_kind'] ?? '')) === 'onpage_aeo_preview_generation';
        if ($generation_focus === 'aeo_content_qa' || $section === 'aeo_content_qa') {
            if (!$is_controlled_aeo_preview) {
                $resolved = $this->aysa_agent8_resolve_onpage_aeo_generation_selection($project_id);
                $resolved_selection = isset($resolved['selection']) && is_array($resolved['selection']) ? $resolved['selection'] : array();
                $resolved_selection_meta = isset($resolved['selection_meta']) && is_array($resolved['selection_meta']) ? $resolved['selection_meta'] : array();
                if ($resolved_selection !== array()) {
                    $selection = $resolved_selection;
                    $payload['selection_meta'] = $resolved_selection_meta;
                    $payload['selection_scope'] = 'global_aeo_generation';
                }
            }
        }
        $requested_count = count($selection);
        $reported_requested_count = max($requested_count, intval($incoming_batch_info['requested_count'] ?? 0));
        $remaining_count = max(0, $reported_requested_count - count($selection));
        $limit_text = static function($value, $limit) {
            $text = trim(wp_strip_all_tags((string) $value));
            if ($text === '') {
                return '';
            }
            if (function_exists('mb_substr')) {
                return mb_substr($text, 0, intval($limit));
            }
            return substr($text, 0, intval($limit));
        };
        $limit_list = static function($items, $limit) use ($limit_text) {
            if (!is_array($items)) {
                return array();
            }
            $values = array();
            foreach ($items as $item) {
                $value = $limit_text($item, 120);
                if ($value !== '') {
                    $values[] = $value;
                }
                if (count($values) >= intval($limit)) {
                    break;
                }
            }
            return array_values(array_unique($values));
        };
        $compact_context = static function($context) use ($limit_text, $limit_list) {
            return array(
                'entity_key' => (string) ($context['entity_key'] ?? ''),
                'entity_id' => intval($context['entity_id'] ?? 0),
                'entity_group' => (string) ($context['entity_group'] ?? ''),
                'object_kind' => (string) ($context['object_kind'] ?? ''),
                'post_type' => (string) ($context['post_type'] ?? ''),
                'taxonomy' => (string) ($context['taxonomy'] ?? ''),
                'page_type' => (string) ($context['page_type'] ?? ''),
                'url' => (string) ($context['url'] ?? ''),
                'title' => $limit_text($context['title'] ?? '', 300),
                'current_title' => $limit_text($context['current_title'] ?? $context['title'] ?? '', 300),
                'current_h1' => $limit_text($context['current_h1'] ?? $context['h1'] ?? '', 300),
                'current_slug' => $limit_text($context['current_slug'] ?? '', 200),
                'main_keyword' => $limit_text($context['main_keyword'] ?? '', 160),
                'primary_keyword' => $limit_text($context['primary_keyword'] ?? $context['main_keyword'] ?? '', 160),
                'keyword' => $limit_text($context['keyword'] ?? $context['primary_keyword'] ?? $context['main_keyword'] ?? '', 160),
                'keywords' => $limit_list($context['keywords'] ?? array(), 12),
                'secondary_keywords' => $limit_list($context['secondary_keywords'] ?? array(), 12),
                'mapping_state' => (string) ($context['mapping_state'] ?? ''),
                'meta_state' => (string) ($context['meta_state'] ?? ''),
                'publication_state' => (string) ($context['publication_state'] ?? ''),
                'current_meta_title' => $limit_text($context['current_meta_title'] ?? '', 300),
                'current_meta_description' => $limit_text($context['current_meta_description'] ?? '', 500),
                'content_excerpt' => $limit_text($context['content_excerpt'] ?? '', 700),
                'content_snippet' => $limit_text($context['content_snippet'] ?? '', 900),
                'current_content_text' => $limit_text($context['current_content_text'] ?? '', 1500),
                'rewrite_state' => (string) ($context['rewrite_state'] ?? ''),
                'rewrite_decision_json' => isset($context['rewrite_decision_json']) && is_array($context['rewrite_decision_json']) ? $context['rewrite_decision_json'] : array(),
                'rewrite_package_json' => isset($context['rewrite_package_json']) && is_array($context['rewrite_package_json']) ? $context['rewrite_package_json'] : array(),
                'featured_image_alt_target' => isset($context['featured_image_alt_target']) && is_array($context['featured_image_alt_target']) ? array(
                    'attachment_id' => intval($context['featured_image_alt_target']['attachment_id'] ?? 0),
                    'image_url' => esc_url_raw((string) ($context['featured_image_alt_target']['image_url'] ?? '')),
                    'image_filename' => $limit_text($context['featured_image_alt_target']['image_filename'] ?? '', 200),
                    'current_alt' => $limit_text($context['featured_image_alt_target']['current_alt'] ?? '', 300),
                    'state' => sanitize_key((string) ($context['featured_image_alt_target']['state'] ?? '')),
                    'primary_keyword' => $limit_text($context['featured_image_alt_target']['primary_keyword'] ?? '', 160),
                ) : array(),
                'featured_image_alt_state' => sanitize_key((string) ($context['featured_image_alt_state'] ?? '')),
                'business_relevance' => sanitize_key((string) ($context['business_relevance'] ?? 'needs_manual_review')),
                'business_relevance_reason' => $limit_text($context['business_relevance_reason'] ?? '', 300),
                'business_relevance_score' => floatval($context['business_relevance_score'] ?? 0),
                'business_relevance_safe_eligible' => !empty($context['business_relevance_safe_eligible']),
                'language' => (string) ($context['language'] ?? ''),
                'country_code' => (string) ($context['country_code'] ?? ''),
            );
        };
        $is_metadata_generation = $generation_focus !== 'aeo_content_qa' && $section !== 'aeo_content_qa';
        $business_relevance_context = $is_metadata_generation
            ? $this->aysa_adjustments_collect_business_relevance_context(0, $project_id)
            : array();
        $seed_entities = array();
        if (isset($payload['seed_projection']['onpage_meta_generation']['entities']) && is_array($payload['seed_projection']['onpage_meta_generation']['entities'])) {
            $seed_entities = $payload['seed_projection']['onpage_meta_generation']['entities'];
        } elseif (isset($payload['selection_meta']) && is_array($payload['selection_meta'])) {
            $seed_entities = $payload['selection_meta'];
        }
        $seed_by_key = array();
        foreach ($seed_entities as $context) {
            if (!is_array($context)) {
                continue;
            }
            $projected_business_relevance = sanitize_key((string) ($context['business_relevance'] ?? ''));
            if ($is_metadata_generation) {
                $context = $this->aysa_agent8_hydrate_onpage_business_relevance_row($context, $business_relevance_context);
            }
            if ($is_content_generation && $projected_business_relevance === 'eligible_business_page') {
                $context['business_relevance'] = 'eligible_business_page';
                $context['business_relevance_safe_eligible'] = true;
            }
            $key = trim((string) ($context['entity_key'] ?? ''));
            if ($key !== '') {
                $seed_by_key[$key] = $compact_context($context);
            }
        }
        $workspace_service = $this->aysa_onpage_workspace_service();
        if ($workspace_service && method_exists($workspace_service, 'build_run_selection_meta') && $selection !== array()) {
            $workspace_contexts = $workspace_service->build_run_selection_meta($project_id, 'all', array(), $selection);
            foreach ($workspace_contexts as $context) {
                if (!is_array($context)) {
                    continue;
                }
                if ($is_metadata_generation) {
                    $context = $this->aysa_agent8_hydrate_onpage_business_relevance_row($context, $business_relevance_context);
                }
                $key = trim((string) ($context['entity_key'] ?? ''));
                if ($key !== '') {
                    $workspace_context = $compact_context($context);
                    if ($is_content_generation && isset($seed_by_key[$key])) {
                        foreach (array('mapping_state', 'main_keyword', 'primary_keyword', 'keyword', 'business_relevance') as $preserved_key) {
                            $workspace_value = trim((string) ($workspace_context[$preserved_key] ?? ''));
                            $seed_value = trim((string) ($seed_by_key[$key][$preserved_key] ?? ''));
                            $preserve_projected_mapping = $preserved_key === 'mapping_state'
                                && in_array($seed_value, array('mapped', 'ai_suggested'), true)
                                && !in_array($workspace_value, array('mapped', 'ai_suggested'), true);
                            $preserve_projected_relevance = $preserved_key === 'business_relevance'
                                && $seed_value === 'eligible_business_page'
                                && $workspace_value !== 'eligible_business_page';
                            if ($workspace_value === '' || $preserve_projected_mapping || $preserve_projected_relevance) {
                                unset($workspace_context[$preserved_key]);
                            }
                        }
                    }
                    $seed_by_key[$key] = array_merge(isset($seed_by_key[$key]) ? $seed_by_key[$key] : array(), $workspace_context);
                }
            }
        }

        $eligible = array();
        $eligible_selection = array();
        $business_relevance_filtered = array();
        foreach ($selection as $entity_key) {
            if (!isset($seed_by_key[$entity_key])) {
                continue;
            }
            $context = $seed_by_key[$entity_key];
            if ($is_metadata_generation && (string) ($context['business_relevance'] ?? '') !== 'eligible_business_page') {
                $business_relevance_filtered[] = array(
                    'entity_key' => $entity_key,
                    'classification' => (string) ($context['business_relevance'] ?? 'needs_manual_review'),
                    'reason' => (string) ($context['business_relevance_reason'] ?? ''),
                );
                continue;
            }
            $eligible[] = $context;
            $eligible_selection[] = $entity_key;
        }
        if ($is_metadata_generation) {
            $selection = $eligible_selection;
        }
        $included_count = count($selection);
        $eligible_count = max($included_count + $remaining_count, intval($incoming_batch_info['eligible_count'] ?? 0));
        unset($payload['selection_meta'], $payload['onpage_workspace_snapshot'], $payload['keyword_mapping_workspace'], $payload['local_keyword_associations'], $payload['onpage_keyword_mapping_diagnostics']);
        $payload['selection'] = array_values($selection);
        $payload['selection_meta'] = $eligible;
        $payload['business_relevance_filtered'] = $business_relevance_filtered;
        $payload['batch_info'] = array(
            'requested_count' => $requested_count,
            'eligible_count' => $eligible_count,
            'included_count' => $included_count,
            'remaining_count' => $remaining_count,
            'batch_size' => $worker_batch_size,
            'worker_batch_size' => $worker_batch_size,
            'execution_mode' => 'worker_incremental',
        );
        $payload['state_summary'] = array(
            'source' => 'agent8_v3_onpage_meta',
            'eligible_count' => $eligible_count,
            'requested_count' => $reported_requested_count,
            'included_count' => $included_count,
            'remaining_count' => $remaining_count,
            'batch_size' => $worker_batch_size,
            'worker_batch_size' => $worker_batch_size,
            'execution_mode' => 'worker_incremental',
            'requires_mapping_state' => 'mapped',
            'requires_primary_keyword' => true,
        );
        $payload['seed_projection'] = array(
            'onpage_meta_generation' => array(
                'eligible_count' => $eligible_count,
                'included_count' => $included_count,
                'remaining_count' => $remaining_count,
                'worker_batch_size' => $worker_batch_size,
                'execution_mode' => 'worker_incremental',
                'entities' => array_map(static function($context) {
                    return array(
                        'entity_key' => (string) ($context['entity_key'] ?? ''),
                        'url' => (string) ($context['url'] ?? ''),
                        'title' => (string) ($context['title'] ?? ''),
                        'h1' => (string) ($context['current_h1'] ?? ''),
                        'main_keyword' => (string) ($context['main_keyword'] ?? ''),
                        'primary_keyword' => (string) ($context['primary_keyword'] ?? $context['main_keyword'] ?? ''),
                        'keyword' => (string) ($context['keyword'] ?? $context['primary_keyword'] ?? $context['main_keyword'] ?? ''),
                        'secondary_keywords' => isset($context['secondary_keywords']) && is_array($context['secondary_keywords']) ? array_values($context['secondary_keywords']) : array(),
                        'mapping_state' => (string) ($context['mapping_state'] ?? ''),
                        'meta_state' => (string) ($context['meta_state'] ?? ''),
                        'publication_state' => (string) ($context['publication_state'] ?? ''),
                        'current_meta_title' => (string) ($context['current_meta_title'] ?? ''),
                        'current_meta_description' => (string) ($context['current_meta_description'] ?? ''),
                        'business_relevance' => (string) ($context['business_relevance'] ?? ''),
                        'business_relevance_reason' => (string) ($context['business_relevance_reason'] ?? ''),
                    );
                }, $eligible),
            ),
        );

        if ($is_content_generation) {
            $prepared_count = count($payload['selection']);
            $original_count = max(0, intval($payload['selected_count'] ?? $requested_count));
            $original_estimate = max(0, intval($payload['estimated_cost_cents'] ?? $payload['estimated_min_cost_cents'] ?? 0));
            $original_batch_total = $original_count > 0 ? intval(ceil($original_count / $worker_batch_size)) : 0;
            $prepared_batch_total = $prepared_count > 0 ? intval(ceil($prepared_count / $worker_batch_size)) : 0;
            if ($prepared_count > 0 && $original_batch_total > 0 && $original_estimate > 0 && $original_estimate % $original_batch_total === 0) {
                $per_batch_estimate = intdiv($original_estimate, $original_batch_total);
                $prepared_estimate = $per_batch_estimate * $prepared_batch_total;
                $payload['selected_count'] = $prepared_count;
                $payload['batch_info']['requested_count'] = $prepared_count;
                $payload['batch_info']['eligible_count'] = $prepared_count;
                $payload['batch_info']['included_count'] = $prepared_count;
                $payload['batch_info']['remaining_count'] = 0;
                $payload['batch_info']['batch_total'] = $prepared_batch_total;
                $payload['estimated_cost_cents'] = $prepared_estimate;
                $payload['estimated_min_cost_cents'] = $prepared_estimate;
                $payload['max_total_cost_cents'] = $prepared_estimate;
                $payload['max_cost_cents'] = $prepared_estimate;
                $idempotency_prefix = $content_confirmation_kind === 'onpage_content_evaluation_generation'
                    ? 'agent8v3:onpage_rewrite_decide:'
                    : 'agent8v3:onpage_rewrite_generate:';
                $payload['idempotency_key'] = $idempotency_prefix . hash('sha256', wp_json_encode(array(
                    'content_generation_server_revalidated_v1',
                    $content_confirmation_kind,
                    $payload['selection'],
                    $prepared_estimate,
                )));
                $payload['server_revalidated_selection'] = true;
                $payload['server_revalidated_original_count'] = $original_count;
                $payload['server_revalidated_prepared_count'] = $prepared_count;
            }
        }

        return $payload;
    }

    private function aysa_agent8_validate_onpage_content_generation_payload($payload, $agent_action) {
        if (!is_array($payload)) {
            return 'onpage_content_payload_invalid';
        }
        $selection = isset($payload['selection']) && is_array($payload['selection'])
            ? array_values(array_unique(array_filter(array_map('strval', $payload['selection']))))
            : array();
        $selection_meta = isset($payload['selection_meta']) && is_array($payload['selection_meta'])
            ? array_values($payload['selection_meta'])
            : array();
        if ($selection === array()) {
            return 'onpage_content_local_revalidation_empty';
        }
        if (intval($payload['selected_count'] ?? 0) !== count($selection)) {
            return 'onpage_content_selection_count_drift';
        }
        if (count($selection_meta) !== count($selection)) {
            return 'onpage_content_selection_metadata_drift';
        }
        $is_decision = sanitize_key((string) $agent_action) === 'generate_onpage_rewrite_decision';
        $confirmation_flag = $is_decision ? 'onpage_content_evaluation_confirmed' : 'onpage_content_rewrite_generation_confirmed';
        if (empty($payload['confirmed_by_user']) || empty($payload[$confirmation_flag])) {
            return 'onpage_content_confirmation_missing';
        }
        $estimate = intval($payload['estimated_cost_cents'] ?? $payload['estimated_min_cost_cents'] ?? 0);
        $cap = intval($payload['max_total_cost_cents'] ?? $payload['max_cost_cents'] ?? 0);
        if ($estimate <= 0 || $cap !== $estimate) {
            return 'onpage_content_exact_cost_cap_drift';
        }
        $meta_by_key = array();
        foreach ($selection_meta as $entity) {
            if (!is_array($entity)) {
                return 'onpage_content_selection_metadata_drift';
            }
            $entity_key = trim((string) ($entity['entity_key'] ?? ''));
            if ($entity_key === '') {
                return 'onpage_content_selection_metadata_drift';
            }
            if ((string) ($entity['business_relevance'] ?? '') !== 'eligible_business_page') {
                return 'onpage_content_business_relevance_drift';
            }
            $keyword = trim((string) ($entity['primary_keyword'] ?? $entity['main_keyword'] ?? $entity['keyword'] ?? ''));
            if ($keyword === '' || !in_array((string) ($entity['mapping_state'] ?? ''), array('mapped', 'ai_suggested'), true)) {
                return 'onpage_content_mapping_drift';
            }
            $meta_by_key[$entity_key] = true;
        }
        foreach ($selection as $entity_key) {
            if (!isset($meta_by_key[$entity_key])) {
                return 'onpage_content_selection_metadata_drift';
            }
        }
        return '';
    }

    private function aysa_agent8_onpage_content_generation_error_message($code) {
        $messages = array(
            'onpage_content_payload_invalid' => 'Payload-ul Content nu este valid. Reîncarcă etapa înainte de a continua.',
            'onpage_content_local_revalidation_empty' => 'WordPress nu a putut reverifica local niciun rând Content eligibil. Selecția nu a fost trimisă și nu s-au debitat credite.',
            'onpage_content_selection_count_drift' => 'Numărul rândurilor Content s-a schimbat la reverificarea locală. Reîncarcă etapa pentru un plafon actualizat.',
            'onpage_content_selection_metadata_drift' => 'Identitatea rândurilor Content nu mai corespunde selecției. Reîncarcă etapa înainte de generare.',
            'onpage_content_confirmation_missing' => 'Confirmarea explicită pentru Content lipsește. Nu s-au trimis date și nu s-au debitat credite.',
            'onpage_content_exact_cost_cap_drift' => 'Estimarea și plafonul Content nu mai sunt identice. Reîncarcă etapa pentru recalculare.',
            'onpage_content_business_relevance_drift' => 'Cel puțin un rând Content nu mai trece verificarea de relevanță pentru business. Selecția a fost oprită înainte de API.',
            'onpage_content_mapping_drift' => 'Cel puțin un rând Content nu mai are keyword și mapping verificabile. Selecția a fost oprită înainte de API.',
        );
        return isset($messages[$code]) ? $messages[$code] : 'Preflight-ul Content a oprit selecția înainte de API. Nu s-au debitat credite.';
    }

    private function aysa_agent8_validate_onpage_aeo_preview_generation_payload($payload) {
        if (!is_array($payload)) {
            return 'onpage_aeo_invalid_payload';
        }
        if (sanitize_key((string) ($payload['section'] ?? '')) !== 'aeo_content_qa') {
            return 'onpage_aeo_section_required';
        }
        if (sanitize_key((string) ($payload['run_type'] ?? '')) !== 'rewrite_generate') {
            return 'onpage_aeo_rewrite_generate_required';
        }
        if (sanitize_key((string) ($payload['generation_focus'] ?? '')) !== 'aeo_content_qa') {
            return 'onpage_aeo_focus_required';
        }
        $selection_scope = sanitize_key((string) ($payload['selection_scope'] ?? ''));
        if (!in_array($selection_scope, array('single_confirmed_mapped_row', 'small_batch_confirmed_mapped_rows'), true)) {
            return 'onpage_aeo_small_batch_scope_required';
        }
        $selection = isset($payload['selection']) && is_array($payload['selection'])
            ? array_values(array_unique(array_filter(array_map('strval', $payload['selection']))))
            : array();
        $selection_count = count($selection);
        if ($selection_count < 1 || $selection_count > 2 || intval($payload['selected_count'] ?? 0) !== $selection_count || intval($payload['selected_rows_count'] ?? 0) !== $selection_count) {
            return 'onpage_aeo_small_batch_required';
        }
        $estimate = intval($payload['estimated_cost_cents'] ?? $payload['estimated_credits_cents'] ?? 0);
        if ($estimate <= 0) {
            return 'onpage_aeo_exact_estimate_required';
        }
        $cap = intval($payload['max_total_cost_cents'] ?? $payload['max_cost_cents'] ?? 0);
        if ($cap <= 0) {
            return 'onpage_aeo_max_cost_cap_required';
        }
        if ($cap < $estimate) {
            return 'onpage_aeo_cap_below_estimate';
        }
        if ($cap > 120) {
            return 'onpage_aeo_cap_above_small_batch_limit';
        }
        if (empty($payload['preview_only']) || empty($payload['generation_preview_only']) || empty($payload['no_apply']) || empty($payload['no_wordpress_write'])) {
            return 'onpage_aeo_preview_only_required';
        }
        if (!empty($payload['writes_enabled']) || !empty($payload['apply_allowed'])) {
            return 'onpage_aeo_writes_disabled_required';
        }
        if (empty($payload['confirmed_by_user']) || empty($payload['onpage_aeo_preview_confirmed'])) {
            return 'onpage_aeo_preview_confirmation_required';
        }
        $selection_meta = isset($payload['selection_meta']) && is_array($payload['selection_meta'])
            ? array_values(array_filter($payload['selection_meta'], 'is_array'))
            : array();
        if (count($selection_meta) !== $selection_count) {
            return 'onpage_aeo_selection_meta_required';
        }
        $meta_by_key = array();
        foreach ($selection_meta as $row) {
            $entity_key = (string) ($row['entity_key'] ?? '');
            if ($entity_key !== '') {
                $meta_by_key[$entity_key] = $row;
            }
        }
        foreach ($selection as $entity_key) {
            if (!isset($meta_by_key[$entity_key]) || !is_array($meta_by_key[$entity_key])) {
                return 'onpage_aeo_selection_meta_mismatch';
            }
            $row = $meta_by_key[$entity_key];
            $url = trim((string) ($row['url'] ?? $row['page_url'] ?? ''));
            $path = wp_parse_url($url, PHP_URL_PATH);
            $path = is_string($path) ? trim($path, '/') : '';
            if ($url === '' || $path === '') {
                return 'onpage_aeo_homepage_excluded';
            }
            $keyword = trim((string) ($row['main_keyword'] ?? $row['primary_keyword'] ?? $row['keyword'] ?? ''));
            if ($keyword === '') {
                return 'onpage_aeo_keyword_required';
            }
            if (sanitize_key((string) ($row['mapping_state'] ?? '')) !== 'mapped') {
                return 'onpage_aeo_mapped_row_required';
            }
            $publication_state = sanitize_key((string) ($row['publication_state'] ?? 'published'));
            if (in_array($publication_state, array('ignored', 'noindex', 'not_indexable', 'draft', 'private', 'trash'), true)) {
                return 'onpage_aeo_published_row_required';
            }
        }

        return '';
    }

    private function aysa_agent8_onpage_aeo_preview_generation_error_message($code) {
        $messages = array(
            'onpage_aeo_invalid_payload' => 'Payloadul pentru preview AEO este invalid.',
            'onpage_aeo_section_required' => 'Preview-ul AEO trebuie să rămână în secțiunea Întrebări și răspunsuri.',
            'onpage_aeo_rewrite_generate_required' => 'Preview-ul AEO poate rula doar ca generare de preview, nu ca aplicare.',
            'onpage_aeo_focus_required' => 'Preview-ul AEO trebuie să folosească focusul de întrebări și răspunsuri.',
            'onpage_aeo_small_batch_scope_required' => 'Preview-ul AEO este permis doar pentru un lot controlat de maximum două rânduri.',
            'onpage_aeo_small_batch_required' => 'Preview-ul AEO este permis pentru unul sau două rânduri selectate.',
            'onpage_aeo_exact_estimate_required' => 'Estimarea exactă este obligatorie înainte de generare.',
            'onpage_aeo_max_cost_cap_required' => 'Limita maximă de cost este obligatorie înainte de generare.',
            'onpage_aeo_cap_below_estimate' => 'Limita maximă de cost este sub estimarea afișată.',
            'onpage_aeo_cap_above_small_batch_limit' => 'Limita maximă de cost depășește limita de 120 credite pentru lotul mic.',
            'onpage_aeo_preview_only_required' => 'Această acțiune trebuie să rămână doar preview, fără aplicare.',
            'onpage_aeo_writes_disabled_required' => 'Scrierile WordPress trebuie să fie dezactivate pentru preview AEO.',
            'onpage_aeo_preview_confirmation_required' => 'Confirmarea explicită este obligatorie pentru preview AEO.',
            'onpage_aeo_selection_meta_required' => 'Lipsește dovada rândului selectat pentru preview AEO.',
            'onpage_aeo_selection_meta_mismatch' => 'Rândul selectat nu se potrivește cu datele de verificare.',
            'onpage_aeo_homepage_excluded' => 'Homepage-ul este exclus din acest preview AEO.',
            'onpage_aeo_keyword_required' => 'Rândul selectat trebuie să aibă keyword mapat.',
            'onpage_aeo_mapped_row_required' => 'Rândul selectat trebuie să fie mapat înainte de preview AEO.',
            'onpage_aeo_published_row_required' => 'Rândul selectat trebuie să fie publicabil/publicat pentru preview AEO.',
        );

        return (string) ($messages[$code] ?? 'Preview-ul AEO este blocat de verificările de siguranță.');
    }

    private function aysa_agent8_resolve_onpage_aeo_generation_selection($project_id) {
        $project_id = intval($project_id);
        $service = $this->aysa_onpage_workspace_service();
        if ($project_id <= 0 || !$service || !method_exists($service, 'build_run_selection_meta')) {
            return array('selection' => array(), 'selection_meta' => array());
        }

        $contexts = $service->build_run_selection_meta($project_id, 'all', array(), array());
        $selection = array();
        $selection_meta = array();
        foreach ($contexts as $context) {
            if (!is_array($context) || !$this->aysa_agent8_is_onpage_aeo_generation_candidate($context)) {
                continue;
            }
            $entity_key = trim((string) ($context['entity_key'] ?? ''));
            if ($entity_key === '') {
                continue;
            }
            $selection[] = $entity_key;
            $selection_meta[] = $context;
        }

        $selection = array_values(array_unique($selection));
        $selection_lookup = array_fill_keys($selection, true);
        $selection_meta = array_values(array_filter($selection_meta, static function($context) use ($selection_lookup) {
            return is_array($context) && isset($selection_lookup[(string) ($context['entity_key'] ?? '')]);
        }));

        return array(
            'selection' => $selection,
            'selection_meta' => $selection_meta,
        );
    }

    private function aysa_agent8_is_onpage_aeo_generation_candidate($context) {
        if (!is_array($context)) {
            return false;
        }
        $entity_key = trim((string) ($context['entity_key'] ?? ''));
        $entity_id = intval($context['entity_id'] ?? 0);
        $entity_group = sanitize_key((string) ($context['entity_group'] ?? $context['post_type'] ?? $context['taxonomy'] ?? ''));
        $publication_state = sanitize_key((string) ($context['publication_state'] ?? 'published'));
        $mapping_state = sanitize_key((string) ($context['mapping_state'] ?? ''));
        $main_keyword = trim((string) ($context['main_keyword'] ?? $context['primary_keyword'] ?? ''));
        if ($entity_key === '' || $entity_id <= 0 || $main_keyword === '') {
            return false;
        }
        if (!in_array($entity_group, array('product', 'ecommerce_category', 'page', 'article'), true)) {
            return false;
        }
        if (in_array($publication_state, array('ignored', 'noindex', 'not_indexable', 'draft', 'private', 'trash', 'mapping_url_unresolved'), true)) {
            return false;
        }
        if ($mapping_state !== 'mapped') {
            return false;
        }

        $package = $this->aysa_agent8_decode_onpage_package_value($context['rewrite_package_json'] ?? array());
        $scope = sanitize_key((string) ($context['optimization_scope'] ?? ''));
        $has_aeo_package = sanitize_key((string) ($package['generation_focus'] ?? '')) === 'aeo_content_qa'
            || trim((string) ($package['aeo_summary_html'] ?? '')) !== ''
            || trim((string) ($package['faq_html'] ?? '')) !== '';
        if ($scope === 'aeo_content_qa' && $has_aeo_package) {
            return false;
        }

        return true;
    }

    private function aysa_agent8_prepare_onpage_keyword_mapping_payload($payload, $project_id) {
        $payload = is_array($payload) ? $payload : array();
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return $payload;
        }

        $selection_scope = sanitize_key((string) ($payload['selection_scope'] ?? 'global_missing_mapping'));
        $selection = isset($payload['selection']) && is_array($payload['selection'])
            ? array_values(array_unique(array_filter(array_map('strval', $payload['selection']))))
            : array();
        $selection_meta = isset($payload['selection_meta']) && is_array($payload['selection_meta'])
            ? array_values(array_filter($payload['selection_meta'], 'is_array'))
            : array();

        if ($selection_scope === 'global_missing_mapping' || $selection === array() || $selection_meta === array()) {
            $resolved = $this->aysa_agent8_resolve_onpage_keyword_mapping_selection($project_id);
            $selection = isset($resolved['selection']) && is_array($resolved['selection']) ? $resolved['selection'] : array();
            $selection_meta = isset($resolved['selection_meta']) && is_array($resolved['selection_meta']) ? $resolved['selection_meta'] : array();
        }

        $worker_batch_size = 5;
        $requested_count = count($selection);
        $batch_total = max(1, (int) ceil($requested_count / $worker_batch_size));
        $keyword_mapping_unit_cost = 15;
        $estimated_total_cost = $keyword_mapping_unit_cost * $batch_total;
        $hash = hash('sha256', wp_json_encode(array('onpage_keyword_mapping_ai_global_v1', $selection, $requested_count)));

        unset($payload['onpage_workspace_snapshot'], $payload['keyword_mapping_workspace'], $payload['local_keyword_associations'], $payload['onpage_keyword_mapping_diagnostics']);
        $payload['section'] = 'keyword_mapping';
        $payload['run_type'] = 'keyword_mapping_ai';
        $payload['selection_scope'] = $selection_scope !== '' ? $selection_scope : 'global_missing_mapping';
        $payload['selection'] = $selection;
        $payload['selection_meta'] = $selection_meta;
        $payload['keyword_source_policy'] = 'existing_research_plus_ai_no_dataforseo';
        $payload['mapping_mode'] = 'ai_mapping_resolver';
        $payload['review_policy'] = 'review_only';
        $payload['no_auto_accept'] = true;
        $payload['worker_batch_size'] = $worker_batch_size;
        $payload['execution_mode'] = 'worker_incremental';
        $payload['estimated_min_cost_cents'] = $estimated_total_cost;
        $payload['estimated_unit_cost_cents'] = $keyword_mapping_unit_cost;
        $payload['batch_info'] = array(
            'requested_count' => $requested_count,
            'eligible_count' => $requested_count,
            'included_count' => $requested_count,
            'remaining_count' => 0,
            'batch_size' => $worker_batch_size,
            'worker_batch_size' => $worker_batch_size,
            'batch_total' => $batch_total,
            'execution_mode' => 'worker_incremental',
        );
        $payload['state_summary'] = array(
            'source' => 'agent8_v3_onpage_keyword_mapping',
            'eligible_count' => $requested_count,
            'requested_count' => $requested_count,
            'included_count' => $requested_count,
            'batch_size' => $worker_batch_size,
            'worker_batch_size' => $worker_batch_size,
            'execution_mode' => 'worker_incremental',
            'billing_action' => 'onpage_keyword_mapping_ai',
            'estimated_min_cost_cents' => $estimated_total_cost,
            'estimated_unit_cost_cents' => $keyword_mapping_unit_cost,
        );
        $payload['seed_projection'] = array(
            'onpage_keyword_mapping_ai' => array(
                'eligible_count' => $requested_count,
                'included_count' => $requested_count,
                'worker_batch_size' => $worker_batch_size,
                'execution_mode' => 'worker_incremental',
                'entities' => $selection_meta,
            ),
        );
        $payload['payload_hash'] = $hash;
        $payload['idempotency_key'] = 'agent8v3:onpage_keyword_mapping_ai:' . $hash;

        return $payload;
    }

    private function aysa_agent8_prepare_onpage_start_payload($payload, $project_id) {
        $payload = is_array($payload) ? $payload : array();
        $project_id = intval($project_id);
        $service = $this->aysa_onpage_workspace_service();
        if ($project_id <= 0 || !$service || !method_exists($service, 'build_run_selection_meta')) {
            $payload['_aysa_onpage_start_error'] = 'onpage_workspace_unavailable';
            $payload['onpage_start_context_status'] = array(
                'status' => 'needs_workspace_sync',
                'reason' => 'workspace_unavailable',
                'selection_count' => 0,
            );
            return $payload;
        }

        $inventory_rows = array();
        $inventory_seen = array();
        $publication_state_counts = array();
        $excluded_publication_state_counts = array();
        $business_relevance_counts = array();
        $business_relevance_context = $this->aysa_adjustments_collect_business_relevance_context(0, $project_id);
        foreach (array('product', 'ecommerce_category', 'page', 'article', 'other') as $entity_group) {
            $workspace_page = 1;
            do {
                $workspace = $service->build_workspace($project_id, array(
                    'entity_group' => $entity_group,
                    'filters' => array(),
                    'page' => $workspace_page,
                    'per_page' => 100,
                    'refresh' => false,
                ));
                $rows = isset($workspace['rows']) && is_array($workspace['rows']) ? $workspace['rows'] : array();
                foreach ($rows as $row) {
                    if (!is_array($row)) {
                        continue;
                    }
                    $entity_key = trim((string) ($row['entity_key'] ?? ''));
                    if ($entity_key === '' || isset($inventory_seen[$entity_key])) {
                        continue;
                    }
                    $inventory_seen[$entity_key] = true;
                    $inventory_rows[] = $this->aysa_agent8_hydrate_onpage_business_relevance_row($row, $business_relevance_context);
                }
                $pagination = isset($workspace['pagination']) && is_array($workspace['pagination']) ? $workspace['pagination'] : array();
                $workspace_pages = max(1, intval($pagination['pages'] ?? 1));
                $workspace_page++;
            } while ($workspace_page <= $workspace_pages);
        }

        $active_rows = array();
        $eligible_rows = array();
        $cleanup_review_total = 0;
        $report_only_total = 0;
        foreach ($inventory_rows as $row) {
            $publication_state = sanitize_key((string) ($row['publication_state'] ?? ''));
            $publication_state = $publication_state !== '' ? $publication_state : 'unknown';
            $publication_state_counts[$publication_state] = intval($publication_state_counts[$publication_state] ?? 0) + 1;
            if ($publication_state !== 'published') {
                $excluded_publication_state_counts[$publication_state] = intval($excluded_publication_state_counts[$publication_state] ?? 0) + 1;
                continue;
            }
            $row_group = sanitize_key((string) ($row['entity_group'] ?? $row['post_type'] ?? $row['taxonomy'] ?? 'other'));
            if (!in_array($row_group, array('product', 'ecommerce_category', 'page', 'article'), true)) {
                $report_only_total++;
                continue;
            }
            $active_rows[] = $row;
            $business_relevance = sanitize_key((string) ($row['business_relevance'] ?? 'needs_manual_review'));
            $business_relevance_counts[$business_relevance] = intval($business_relevance_counts[$business_relevance] ?? 0) + 1;
            if ($business_relevance !== 'eligible_business_page') {
                if (in_array($business_relevance, array('cleanup_review', 'likely_irrelevant_legacy_or_test', 'questionable_relevance'), true)) {
                    $cleanup_review_total++;
                } else {
                    $report_only_total++;
                }
                continue;
            }
            $entity_id = intval($row['entity_id'] ?? $row['id_intern'] ?? $row['post_id'] ?? 0);
            $target_url = trim((string) ($row['url'] ?? $row['page_url'] ?? $row['permalink'] ?? ''));
            if ($entity_id <= 0 || $target_url === '') {
                $report_only_total++;
                continue;
            }
            $eligible_rows[] = $row;
        }

        $eligible_keys = array_values(array_map(static function($row) {
            return (string) ($row['entity_key'] ?? '');
        }, $eligible_rows));
        $inventory_hash = hash('sha256', wp_json_encode($eligible_keys));
        $batch_size = max(1, min(80, intval($payload['batch_size'] ?? 80)));
        $cursor_offset = 0;
        $continuation_token = trim((string) ($payload['continuation_token'] ?? ''));
        if ($continuation_token !== '') {
            $decoded_token = base64_decode(strtr($continuation_token, '-_', '+/'), true);
            $decoded_cursor = is_string($decoded_token) ? json_decode($decoded_token, true) : null;
            if (!is_array($decoded_cursor)
                || intval($decoded_cursor['offset'] ?? -1) < 0
                || !hash_equals(substr($inventory_hash, 0, 16), (string) ($decoded_cursor['inventory'] ?? ''))) {
                $payload['_aysa_onpage_start_error'] = 'onpage_continuation_token_invalid';
                return $payload;
            }
            $cursor_offset = intval($decoded_cursor['offset']);
        }

        $selected_rows = array_slice($eligible_rows, $cursor_offset, $batch_size);
        $selection = array_values(array_filter(array_map(static function($row) {
            return trim((string) ($row['entity_key'] ?? ''));
        }, $selected_rows)));
        $selection_meta = array_values(array_map(function($row) {
            return $this->aysa_agent8_compact_onpage_keyword_mapping_entity_context($row);
        }, $selected_rows));
        $next_offset = $cursor_offset + count($selection);
        $next_continuation_token = '';
        if ($next_offset < count($eligible_rows)) {
            $next_continuation_token = rtrim(strtr(base64_encode(wp_json_encode(array(
                'offset' => $next_offset,
                'inventory' => substr($inventory_hash, 0, 16),
            ))), '+/', '-_'), '=');
        }
        $coverage = array(
            'inventory_total' => count($inventory_rows),
            'active_total' => count($active_rows),
            'eligible_total' => count($eligible_rows),
            'selected_total' => count($selection),
            'processed_total' => 0,
            'generated_metadata_total' => 0,
            'generated_rewrite_total' => 0,
            'reviewable_total' => 0,
            'executable_total' => 0,
            'applied_total' => 0,
            'skipped_total' => max(0, count($active_rows) - count($eligible_rows)),
            'cleanup_review_total' => $cleanup_review_total,
            'report_only_total' => $report_only_total,
            'current_batch_number' => intval(floor($cursor_offset / $batch_size)) + 1,
            'total_batches_estimate' => max(1, intval(ceil(count($eligible_rows) / $batch_size))),
            'coverage_scope' => count($selection) < count($eligible_rows) ? 'batch' : 'full_inventory',
            'batch_size' => $batch_size,
            'cursor_offset' => $cursor_offset,
            'unprocessed_total' => max(0, count($eligible_rows) - $next_offset),
            'continuation_token' => $continuation_token,
            'next_continuation_token' => $next_continuation_token,
            'publication_state_counts' => $publication_state_counts,
            'excluded_publication_state_counts' => $excluded_publication_state_counts,
            'business_relevance_counts' => $business_relevance_counts,
        );
        if ($selection === array() || $selection_meta === array()) {
            $payload['_aysa_onpage_start_error'] = 'onpage_workspace_empty';
            $payload['onpage_start_context_status'] = array(
                'status' => 'needs_workspace_sync',
                'reason' => 'empty_workspace',
                'selection_count' => 0,
            );
            return $payload;
        }

        $hash = hash('sha256', wp_json_encode(array('agent8v3_onpage_start_v2', $inventory_hash, $cursor_offset, $selection)));
        $payload['onpage_workspace_snapshot'] = array(
            'summary' => array(
                'source' => 'agent8_v3_onpage_start',
                'selection_count' => count($selection),
                'coverage' => $coverage,
            ),
            'rows' => $selection_meta,
            'agent8_start_snapshot' => true,
        );
        $payload['seed_projection'] = array(
            'run_type' => 'onpage',
            'run_status' => 'completed',
            'current_task_key' => 'plugin_projection_sync',
            'current_milestone' => 'Projection prepared from plugin workspace',
            'onpage_meta_generation' => array(
                'eligible_count' => count($selection),
                'included_count' => count($selection),
                'entities' => $selection_meta,
            ),
            'inventory_rows' => $selection_meta,
            'state_summary' => array(
                'source' => 'agent8_v3_onpage_start',
                'selection_count' => count($selection),
                'coverage' => $coverage,
            ),
        );
        $payload['selection'] = $selection;
        $payload['selection_meta'] = $selection_meta;
        $payload['onpage_coverage'] = $coverage;
        foreach ($coverage as $coverage_key => $coverage_value) {
            $payload[$coverage_key] = $coverage_value;
        }
        $payload['next_continuation_token'] = $next_continuation_token;
        $payload['workspace_hash'] = $hash;
        $payload['payload_hash'] = $hash;
        $approved_idempotency_key = trim((string) ($payload['idempotency_key'] ?? ''));
        if (strpos($approved_idempotency_key, 'agent8v3:onpage_analysis_start:') !== 0) {
            $payload['idempotency_key'] = 'agent8v3:onpage_start:' . $hash;
        }
        $payload['run_source'] = 'agent8_v3_onpage_workspace';
        $payload['onpage_start_context_status'] = array(
            'status' => 'ready',
            'selection_count' => count($selection),
            'workspace_hash' => $hash,
            'coverage_scope' => (string) $coverage['coverage_scope'],
            'current_batch_number' => intval($coverage['current_batch_number']),
            'total_batches_estimate' => intval($coverage['total_batches_estimate']),
            'unprocessed_total' => intval($coverage['unprocessed_total']),
        );

        return $payload;
    }

    private function aysa_agent8_resolve_onpage_keyword_mapping_selection($project_id) {
        $project_id = intval($project_id);
        $service = $this->aysa_onpage_workspace_service();
        if ($project_id <= 0 || !$service || !method_exists($service, 'build_run_selection_meta')) {
            return array('selection' => array(), 'selection_meta' => array());
        }

        $workspace_rows = $this->aysa_agent8_get_onpage_keyword_mapping_needed_rows($project_id);
        $workspace_selection = array_values(array_filter(array_map(static function($row) {
            return is_array($row) ? trim((string) ($row['entity_key'] ?? '')) : '';
        }, $workspace_rows)));
        if ($workspace_selection === array()) {
            return array('selection' => array(), 'selection_meta' => array());
        }

        $contexts = $service->build_run_selection_meta($project_id, 'all', array(), $workspace_selection);
        $context_lookup = array();
        foreach ($contexts as $context) {
            if (is_array($context)) {
                $context_key = trim((string) ($context['entity_key'] ?? ''));
                if ($context_key !== '') {
                    $context_lookup[$context_key] = $context;
                }
            }
        }
        $selection = array();
        $selection_meta = array();
        $allowed_groups = array('product', 'ecommerce_category', 'category', 'product_cat', 'page', 'article', 'post', 'other');
        $blocked_publication = array('ignored', 'noindex', 'not_indexable', 'draft', 'private', 'trash', 'mapping_url_unresolved');
        foreach ($workspace_rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $row_key = trim((string) ($row['entity_key'] ?? ''));
            $context = isset($context_lookup[$row_key]) && is_array($context_lookup[$row_key]) ? $context_lookup[$row_key] : $row;
            $entity_key = trim((string) ($context['entity_key'] ?? ''));
            $entity_id = intval($context['entity_id'] ?? 0);
            $mapping_state = sanitize_key((string) ($context['mapping_state'] ?? ''));
            $main_keyword = trim((string) ($context['main_keyword'] ?? $context['primary_keyword'] ?? ''));
            $publication_state = sanitize_key((string) ($context['publication_state'] ?? 'published'));
            $entity_group = sanitize_key((string) ($context['entity_group'] ?? $context['post_type'] ?? $context['taxonomy'] ?? ''));
            if ($entity_key === '' || $entity_id <= 0 || in_array($publication_state, $blocked_publication, true)) {
                continue;
            }
            if ($entity_group !== '' && !in_array($entity_group, $allowed_groups, true)) {
                continue;
            }
            if ($main_keyword !== '' && !in_array($mapping_state, array('mapping_required', 'mapping_review', 'needs_mapping', 'onpage_mapping_required'), true)) {
                continue;
            }

            $selection[] = $entity_key;
            $selection_meta[] = $this->aysa_agent8_compact_onpage_keyword_mapping_entity_context($context);
        }

        $selection = array_values(array_unique($selection));
        $selection_lookup = array_fill_keys($selection, true);
        $selection_meta = array_values(array_filter($selection_meta, static function($context) use ($selection_lookup) {
            return is_array($context) && isset($selection_lookup[(string) ($context['entity_key'] ?? '')]);
        }));

        return array(
            'selection' => $selection,
            'selection_meta' => $selection_meta,
        );
    }

    private function aysa_agent8_get_onpage_keyword_mapping_needed_keys($project_id) {
        $rows = $this->aysa_agent8_get_onpage_keyword_mapping_needed_rows($project_id);
        $selection = array();
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $entity_key = trim((string) ($row['entity_key'] ?? ''));
            if ($entity_key !== '') {
                $selection[] = $entity_key;
            }
        }
        return array_values(array_unique($selection));
    }

    private function aysa_agent8_get_onpage_keyword_mapping_needed_rows($project_id) {
        $project_id = intval($project_id);
        $service = $this->aysa_onpage_workspace_service();
        if ($project_id <= 0 || !$service || !method_exists($service, 'build_workspace')) {
            return array();
        }

        $rows_out = array();
        $seen = array();
        foreach (array('product', 'ecommerce_category', 'page', 'article') as $entity_group) {
            $page = 1;
            do {
                $workspace = $service->build_workspace($project_id, array(
                    'entity_group' => $entity_group,
                    'filters' => array('view' => 'keyword_mapping_needed'),
                    'page' => $page,
                    'per_page' => 100,
                    'refresh' => false,
                ));
                $rows = isset($workspace['rows']) && is_array($workspace['rows']) ? $workspace['rows'] : array();
                foreach ($rows as $row) {
                    if (!is_array($row)) {
                        continue;
                    }
                    $entity_key = trim((string) ($row['entity_key'] ?? ''));
                    if ($entity_key === '' || isset($seen[$entity_key])) {
                        continue;
                    }
                    $target_url = trim((string) ($row['page_url'] ?? $row['url'] ?? ''));
                    if ($target_url !== '' && $this->aysa_keyword_mapping_execution_is_homepage_url($target_url)) {
                        continue;
                    }
                    $seen[$entity_key] = true;
                    $rows_out[] = $row;
                }
                $pagination = isset($workspace['pagination']) && is_array($workspace['pagination']) ? $workspace['pagination'] : array();
                $pages = max(1, intval($pagination['pages'] ?? 1));
                $page++;
            } while ($page <= $pages);
        }

        return $rows_out;
    }

    private function aysa_agent8_compact_onpage_keyword_mapping_entity_context($context) {
        $context = is_array($context) ? $context : array();
        $limit_text = static function($value, $limit) {
            $text = trim(wp_strip_all_tags((string) $value));
            if ($text === '') {
                return '';
            }
            if (function_exists('mb_substr')) {
                return mb_substr($text, 0, intval($limit));
            }
            return substr($text, 0, intval($limit));
        };
        $limit_list = static function($items, $limit) use ($limit_text) {
            if (!is_array($items)) {
                return array();
            }
            $values = array();
            foreach ($items as $item) {
                $value = $limit_text($item, 120);
                if ($value !== '') {
                    $values[] = $value;
                }
                if (count($values) >= intval($limit)) {
                    break;
                }
            }
            return array_values(array_unique($values));
        };

        return array(
            'entity_key' => (string) ($context['entity_key'] ?? ''),
            'entity_id' => intval($context['entity_id'] ?? 0),
            'entity_group' => (string) ($context['entity_group'] ?? ''),
            'object_kind' => (string) ($context['object_kind'] ?? ''),
            'post_type' => (string) ($context['post_type'] ?? ''),
            'taxonomy' => (string) ($context['taxonomy'] ?? ''),
            'page_type' => (string) ($context['page_type'] ?? ''),
            'url' => esc_url_raw((string) ($context['url'] ?? '')),
            'title' => $limit_text($context['title'] ?? '', 240),
            'current_title' => $limit_text($context['current_title'] ?? $context['title'] ?? '', 240),
            'current_h1' => $limit_text($context['current_h1'] ?? $context['h1'] ?? '', 240),
            'main_keyword' => $limit_text($context['main_keyword'] ?? '', 160),
            'secondary_keywords' => $limit_list($context['secondary_keywords'] ?? array(), 12),
            'mapping_state' => (string) ($context['mapping_state'] ?? ''),
            'publication_state' => (string) ($context['publication_state'] ?? ''),
            'business_relevance' => (string) ($context['business_relevance'] ?? 'needs_manual_review'),
            'business_relevance_reason' => $limit_text($context['business_relevance_reason'] ?? '', 300),
            'business_relevance_score' => floatval($context['business_relevance_score'] ?? 0),
            'business_relevance_safe_eligible' => !empty($context['business_relevance_safe_eligible']),
            'current_meta_description' => $limit_text($context['current_meta_description'] ?? '', 500),
            'content_excerpt' => $limit_text($context['content_excerpt'] ?? '', 700),
            'content_snippet' => $limit_text($context['content_snippet'] ?? $context['current_content_text'] ?? '', 1800),
            'language' => (string) ($context['language'] ?? ''),
            'country_code' => (string) ($context['country_code'] ?? ''),
        );
    }

    private function aysa_agent8_prepare_onpage_meta_apply_payload($payload, $project_id) {
        $payload = is_array($payload) ? $payload : array();
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return $payload;
        }

        $rows = array();
        $decode_package = static function($value) {
            if (is_array($value)) {
                return $value;
            }
            if (is_string($value) && trim($value) !== '') {
                $decoded = json_decode($value, true);
                return is_array($decoded) ? $decoded : array();
            }
            return array();
        };
        $first_string = static function($source, $keys) {
            if (!is_array($source)) {
                return '';
            }
            foreach ($keys as $key) {
                if (isset($source[$key]) && is_scalar($source[$key])) {
                    $value = trim((string) $source[$key]);
                    if ($value !== '') {
                        return $value;
                    }
                }
            }
            return '';
        };

        $items = array();
        $selection = array();
        $seen = array();
        $business_relevance_filtered = array();
        $provided_items = isset($payload['accepted_items']) && is_array($payload['accepted_items']) ? $payload['accepted_items'] : array();
        if ($provided_items === array()) {
            $payload['_aysa_onpage_apply_error'] = 'onpage_apply_requires_selected_items';
            return $payload;
        }
        $qa_smoke_mode = !empty($payload['qa_smoke_mode'])
            || (string) ($payload['selection_scope'] ?? '') === 'qa_smoke_max_three';
        $apply_batch_limit = $qa_smoke_mode ? 3 : 80;
        $provided_items = array_values($provided_items);
        if (count($provided_items) > $apply_batch_limit) {
            $payload['_aysa_onpage_apply_error'] = $qa_smoke_mode
                ? 'onpage_qa_smoke_max_three_required'
                : 'onpage_apply_batch_limit_exceeded';
            return $payload;
        }
        $provided_keys = array();
        $provided_expectations = array();
        foreach ($provided_items as $provided_item) {
            if (!is_array($provided_item)) {
                continue;
            }
            $provided_key = trim((string) ($provided_item['entity_key'] ?? $provided_item['item_id'] ?? ''));
            if ($provided_key !== '') {
                $provided_keys[$provided_key] = true;
                $provided_expectations[$provided_key] = array(
                    'provider_output_id' => sanitize_text_field((string) ($provided_item['provider_output_id'] ?? '')),
                    'proposal_hash' => sanitize_text_field((string) ($provided_item['proposal_hash'] ?? '')),
                );
            }
        }
        $provided_status = array();
        foreach (array_keys($provided_keys) as $provided_key) {
            $provided_title = '';
            if (preg_match('/^post:(\d+)$/', $provided_key, $provided_match)) {
                $provided_title = sanitize_text_field((string) get_the_title(intval($provided_match[1])));
            }
            $provided_status[$provided_key] = array(
                'entity_key' => $provided_key,
                'title' => $provided_title,
                'reason' => 'workspace_row_missing',
            );
        }
        $service = $this->aysa_onpage_workspace_service();
        if (!$service || !method_exists($service, 'build_run_selection_meta')) {
            $payload['_aysa_onpage_apply_error'] = 'onpage_workspace_unavailable';
            return $payload;
        }
        $business_relevance_context = $this->aysa_adjustments_collect_business_relevance_context(0, $project_id);
        $contexts = $service->build_run_selection_meta($project_id, 'all', array(), array_keys($provided_keys));
        foreach ($contexts as $context) {
            if (!is_array($context)) {
                continue;
            }
            $context = $this->aysa_agent8_canonicalize_onpage_row_url($context);
            $context = $this->aysa_agent8_hydrate_onpage_metadata_route_evidence_row($context);
            $context = $this->aysa_agent8_hydrate_onpage_applied_meta_row($context);
            $rows[] = $this->aysa_agent8_hydrate_onpage_business_relevance_row($context, $business_relevance_context);
        }
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $entity_key = trim((string) ($row['entity_key'] ?? ''));
            if ($entity_key === '' || isset($seen[$entity_key])) {
                continue;
            }
            if ($provided_keys !== array() && !isset($provided_keys[$entity_key])) {
                continue;
            }
            if (isset($provided_status[$entity_key])) {
                $provided_status[$entity_key]['title'] = sanitize_text_field((string) ($row['title'] ?? $row['current_title'] ?? ''));
            }
            $package = $decode_package($row['meta_package_json'] ?? ($row['meta_package'] ?? array()));
            $title = $first_string($package, array('title', 'meta_title'));
            $description = $first_string($package, array('description', 'meta_description'));
            $expectation = isset($provided_expectations[$entity_key]) && is_array($provided_expectations[$entity_key])
                ? $provided_expectations[$entity_key]
                : array();
            $expected_proposal_hash = trim((string) ($expectation['proposal_hash'] ?? ''));
            $expected_provider_output_id = trim((string) ($expectation['provider_output_id'] ?? ''));
            $current_proposal_hash = hash('sha256', $entity_key . "\n" . $title . "\n" . $description);
            if ((string) ($payload['selection_scope'] ?? '') === 'durable_review_queue') {
                if ($expected_proposal_hash === '' || $expected_provider_output_id === '') {
                    if (isset($provided_status[$entity_key])) {
                        $provided_status[$entity_key]['reason'] = 'durable_proposal_evidence_missing';
                    }
                    continue;
                }
                if (!hash_equals($expected_proposal_hash, $current_proposal_hash)) {
                    if (isset($provided_status[$entity_key])) {
                        $provided_status[$entity_key]['reason'] = 'durable_proposal_drift';
                    }
                    continue;
                }
                if (preg_match('/^(\d+):/', $expected_provider_output_id, $provider_output_match)) {
                    $expected_run_id = intval($provider_output_match[1]);
                    $package_run_id = intval($package['worker_run_id'] ?? 0);
                    if ($expected_run_id <= 0 || $package_run_id !== $expected_run_id) {
                        if (isset($provided_status[$entity_key])) {
                            $provided_status[$entity_key]['reason'] = 'durable_provider_output_run_mismatch';
                        }
                        continue;
                    }
                }
            }
            $business_relevance = sanitize_key((string) ($row['business_relevance'] ?? 'needs_manual_review'));
            $publication_state = sanitize_key((string) ($row['publication_state'] ?? ''));
            $mapping_state = sanitize_key((string) ($row['mapping_state'] ?? ''));
            $object_kind = sanitize_key((string) ($row['object_kind'] ?? 'post'));
            $row_url = esc_url_raw((string) ($row['url'] ?? $row['permalink'] ?? ''));
            $row_path = trim((string) wp_parse_url($row_url, PHP_URL_PATH), '/');
            $explicit_review_override = isset($provided_keys[$entity_key])
                && in_array($business_relevance, array('questionable_relevance', 'needs_manual_review'), true)
                && in_array($publication_state, array('publish', 'published'), true)
                && $mapping_state === 'mapped'
                && $object_kind === 'post'
                && $row_path !== ''
                && !empty($row['metadata_route_evidence_available'])
                && !empty($row['metadata_old_value_captured'])
                && ($title !== '' || $description !== '');
            if ($business_relevance !== 'eligible_business_page' && !$explicit_review_override) {
                if (isset($provided_status[$entity_key])) {
                    $provided_status[$entity_key]['reason'] = 'business_relevance_review_required';
                }
                $business_relevance_filtered[] = array(
                    'entity_key' => $entity_key,
                    'classification' => (string) ($row['business_relevance'] ?? 'needs_manual_review'),
                    'reason' => (string) ($row['business_relevance_reason'] ?? ''),
                );
                continue;
            }
            if ($explicit_review_override && isset($provided_status[$entity_key])) {
                $provided_status[$entity_key]['reason'] = 'eligible_after_explicit_review';
            }
            $meta_state = sanitize_key((string) ($row['meta_state'] ?? ''));
            $output_state = sanitize_key((string) ($row['output_state'] ?? ''));
            if ($meta_state === 'applied' || $output_state === 'meta_applied') {
                if (isset($provided_status[$entity_key])) {
                    $provided_status[$entity_key]['reason'] = 'already_applied';
                }
                continue;
            }
            if ($title === '' && $description === '') {
                if (isset($provided_status[$entity_key])) {
                    $provided_status[$entity_key]['reason'] = 'proposal_package_missing';
                }
                continue;
            }
            $seen[$entity_key] = true;
            $selection[] = $entity_key;
            $items[] = array(
                'item_id' => $entity_key,
                'entity_key' => $entity_key,
                'provider_output_id' => $expected_provider_output_id,
                'proposal_hash' => $expected_proposal_hash,
                'operation' => 'apply_meta',
            );
            if (isset($provided_status[$entity_key])) {
                $provided_status[$entity_key]['reason'] = 'eligible';
            }
        }

        if ($items === array()) {
            if ($business_relevance_filtered !== array()) {
                $payload['_aysa_onpage_apply_error'] = 'onpage_business_relevance_required';
                $payload['business_relevance_filtered'] = $business_relevance_filtered;
            }
            return $payload;
        }

        $hash_items = array_map(static function($item) {
            $entity_key = is_array($item) ? (string) ($item['entity_key'] ?? '') : '';
            return array(
                'item_id' => $entity_key,
                'entity_key' => $entity_key,
                'provider_output_id' => is_array($item) ? (string) ($item['provider_output_id'] ?? '') : '',
                'proposal_hash' => is_array($item) ? (string) ($item['proposal_hash'] ?? '') : '',
            );
        }, $items);
        usort($hash_items, static function($left, $right) {
            return strcmp((string) ($left['entity_key'] ?? ''), (string) ($right['entity_key'] ?? ''));
        });
        $hash = hash('sha256', wp_json_encode($hash_items));
        $expected_workspace_hash = trim((string) ($payload['workspace_hash'] ?? ''));
        if ($expected_workspace_hash !== '' && !hash_equals($expected_workspace_hash, $hash)) {
            $payload['_aysa_onpage_apply_error'] = 'onpage_workspace_hash_mismatch';
            $eligible_keys = array_values(array_filter(array_map(static function($item) {
                return is_array($item) ? trim((string) ($item['entity_key'] ?? '')) : '';
            }, $hash_items)));
            $provided_key_list = array_values(array_keys($provided_keys));
            sort($eligible_keys, SORT_STRING);
            sort($provided_key_list, SORT_STRING);
            $payload['_aysa_onpage_apply_error_context'] = array(
                'selected_count' => count($provided_key_list),
                'eligible_count' => count($eligible_keys),
                'missing_entity_keys' => array_values(array_diff($provided_key_list, $eligible_keys)),
                'unexpected_entity_keys' => array_values(array_diff($eligible_keys, $provided_key_list)),
                'changed_rows' => array_values(array_filter($provided_status, static function($status) {
                    return is_array($status) && (string) ($status['reason'] ?? '') !== 'eligible';
                })),
            );
            return $payload;
        }
        unset($payload['onpage_workspace_snapshot'], $payload['keyword_mapping_workspace'], $payload['local_keyword_associations'], $payload['onpage_keyword_mapping_diagnostics']);
        $payload['selection'] = array_values(array_map(static function($item) {
            return (string) ($item['entity_key'] ?? '');
        }, $hash_items));
        $payload['accepted_items'] = array_values(array_map(static function($item) {
            return array(
                'item_id' => (string) ($item['entity_key'] ?? ''),
                'entity_key' => (string) ($item['entity_key'] ?? ''),
                'provider_output_id' => (string) ($item['provider_output_id'] ?? ''),
                'proposal_hash' => (string) ($item['proposal_hash'] ?? ''),
                'operation' => 'apply_meta',
            );
        }, $hash_items));
        $payload['write_class'] = 'onpage_metadata_apply';
        $payload['workspace_hash'] = $hash;
        $payload['payload_hash'] = $hash;
        $payload['idempotency_key'] = 'agent8v3:onpage_meta_apply:' . $hash;
        $payload['billing_policy'] = 'verified_changed_onpage_fields_only_cost_x5';
        $payload['billing_unit_cents'] = 5;
        $computed_max_cost_cents = count($items) * 2 * 5;
        $approved_max_cost_cents = intval($payload['approved_max_total_cost_cents'] ?? 0);
        if ($approved_max_cost_cents <= 0 || $approved_max_cost_cents !== $computed_max_cost_cents) {
            $payload['_aysa_onpage_apply_error'] = 'onpage_approved_cost_cap_mismatch';
            $payload['_aysa_onpage_apply_error_context'] = array(
                'selected_count' => count($items),
                'approved_max_total_cost_cents' => $approved_max_cost_cents,
                'computed_max_total_cost_cents' => $computed_max_cost_cents,
            );
            return $payload;
        }
        $payload['max_cost_cents'] = $approved_max_cost_cents;
        $payload['max_total_cost_cents'] = $approved_max_cost_cents;
        $provider_run_ids = array();
        foreach ($hash_items as $hash_item) {
            $provider_output_id = sanitize_text_field((string) ($hash_item['provider_output_id'] ?? ''));
            if (preg_match('/^(\d+):/', $provider_output_id, $provider_run_match)) {
                $provider_run_ids[intval($provider_run_match[1])] = true;
            }
        }
        if (count($provider_run_ids) === 1) {
            $payload['source_metadata_run_id'] = intval(array_key_first($provider_run_ids));
        }
        $payload['apply_scope'] = 'server_workspace_generated_meta';
        $payload['apply_scope_count'] = count($items);
        $payload['selection_scope'] = $qa_smoke_mode ? 'qa_smoke_max_three' : 'durable_review_queue';
        $payload['qa_smoke_mode'] = $qa_smoke_mode;
        $payload['business_relevance_filtered'] = $business_relevance_filtered;

        return $payload;
    }

    private function aysa_agent8_prepare_onpage_aeo_apply_payload($payload, $project_id) {
        $payload = is_array($payload) ? $payload : array();
        $project_id = intval($project_id);
        $service = $this->aysa_onpage_workspace_service();
        if ($project_id <= 0 || !$service || !method_exists($service, 'build_run_selection_meta')) {
            return $payload;
        }

        $contexts = $service->build_run_selection_meta($project_id, 'all', array(), array());
        $items = array();
        $selection = array();
        $seen = array();
        $provided_items = isset($payload['accepted_items']) && is_array($payload['accepted_items']) ? $payload['accepted_items'] : array();
        $provided_keys = array();
        foreach ($provided_items as $provided_item) {
            if (!is_array($provided_item)) {
                continue;
            }
            $provided_key = trim((string) ($provided_item['entity_key'] ?? $provided_item['item_id'] ?? ''));
            if ($provided_key !== '') {
                $provided_keys[$provided_key] = true;
            }
        }
        foreach ($contexts as $context) {
            if (!is_array($context) || !$this->aysa_agent8_is_onpage_aeo_apply_candidate($context)) {
                continue;
            }
            $entity_key = trim((string) ($context['entity_key'] ?? ''));
            if ($entity_key === '' || isset($seen[$entity_key])) {
                continue;
            }
            if ($provided_keys !== array() && !isset($provided_keys[$entity_key])) {
                continue;
            }
            $seen[$entity_key] = true;
            $selection[] = $entity_key;
            $items[] = array(
                'item_id' => $entity_key,
                'entity_key' => $entity_key,
                'operation' => 'apply_rewrite',
            );
        }

        if ($items === array()) {
            return $payload;
        }

        $provided_items = isset($payload['accepted_items']) && is_array($payload['accepted_items']) ? $payload['accepted_items'] : array();
        $hash = hash('sha256', wp_json_encode(array('onpage_aeo_apply_global_v1', $items)));
        $expected_workspace_hash = trim((string) ($payload['workspace_hash'] ?? ''));
        if ($provided_items !== array() && $expected_workspace_hash !== '' && !hash_equals($expected_workspace_hash, $hash)) {
            $payload['_aysa_onpage_apply_error'] = 'onpage_workspace_hash_mismatch';
            return $payload;
        }
        unset($payload['onpage_workspace_snapshot'], $payload['keyword_mapping_workspace'], $payload['local_keyword_associations'], $payload['onpage_keyword_mapping_diagnostics']);
        $payload['selection'] = array_values(array_unique($selection));
        $payload['accepted_items'] = $items;
        $payload['write_class'] = 'onpage_aeo_appendix_apply';
        $payload['workspace_hash'] = $hash;
        $payload['payload_hash'] = $hash;
        $payload['idempotency_key'] = 'agent8v3:onpage_aeo_apply:' . $hash;
        $payload['apply_scope'] = 'server_workspace_aeo_safe_appendix';
        $payload['apply_scope_count'] = count($items);

        return $payload;
    }

    private function aysa_agent8_is_onpage_aeo_apply_candidate($context) {
        if (!is_array($context)) {
            return false;
        }
        $entity_key = trim((string) ($context['entity_key'] ?? ''));
        $entity_id = intval($context['entity_id'] ?? 0);
        $object_kind = strtolower(trim((string) ($context['object_kind'] ?? 'post')));
        $entity_group = sanitize_key((string) ($context['entity_group'] ?? $context['post_type'] ?? $context['taxonomy'] ?? ''));
        if ($entity_key === '' || $entity_id <= 0 || $object_kind === 'term' || !in_array($entity_group, array('product', 'ecommerce_category', 'page', 'article'), true)) {
            return false;
        }
        $package = $this->aysa_agent8_decode_onpage_package_value($context['rewrite_package_json'] ?? array());
        $has_aeo_package = sanitize_key((string) ($package['generation_focus'] ?? '')) === 'aeo_content_qa'
            || trim((string) ($package['aeo_summary_html'] ?? '')) !== ''
            || trim((string) ($package['faq_html'] ?? '')) !== '';
        if (!$has_aeo_package) {
            return false;
        }
        if (empty($package['safe_to_auto_apply']) && !in_array('seo_appendix', array_map('strval', is_array($package['apply_targets'] ?? null) ? $package['apply_targets'] : array()), true)) {
            return false;
        }
        if (!empty($context['seo_appendix_applied']) || trim((string) get_post_meta($entity_id, '_aysa_seo_appendix_html', true)) !== '') {
            return false;
        }

        return true;
    }

    private function aysa_agent8_attach_onpage_keyword_mapping_context($payload, $project_id) {
        $payload = is_array($payload) ? $payload : array();
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return $payload;
        }

        $workspace = $this->aysa_keyword_v2_cache_get($project_id, 'keyword_mapping_v2');
        $associations = $this->get_keyword_discovery_local_associations($project_id);
        $payload['keyword_mapping_workspace'] = is_array($workspace) ? $workspace : array();
        $payload['local_keyword_associations'] = is_array($associations) ? array_values($associations) : array();
        $diagnostics = $this->aysa_agent8_onpage_keyword_mapping_diagnostics(
            $project_id,
            $payload['keyword_mapping_workspace'],
            $payload['local_keyword_associations']
        );
        if (isset($payload['onpage_workspace_snapshot']['rows']) && is_array($payload['onpage_workspace_snapshot']['rows'])) {
            $diagnostics['modal_rows_count'] = count($payload['onpage_workspace_snapshot']['rows']);
        }
        $diagnostics['global_missing_mapping_count'] = $this->aysa_agent8_count_onpage_keyword_mapping_needed($project_id);
        $payload['onpage_keyword_mapping_diagnostics'] = $diagnostics;
        $payload['onpage_keyword_mapping_targets'] = array();

        return $payload;
    }

    private function aysa_agent8_count_onpage_keyword_mapping_needed($project_id) {
        return count($this->aysa_agent8_get_onpage_keyword_mapping_needed_keys($project_id));
    }

    private function aysa_agent8_resolve_onpage_agent_decision($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array();
        }

        $workspace = $this->aysa_agent8_get_onpage_workspace_snapshot($project_id);
        $rows = isset($workspace['rows']) && is_array($workspace['rows']) ? array_values($workspace['rows']) : array();
        $needs_attention = 0;
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $mapping_state = sanitize_key((string) ($row['mapping_state'] ?? $row['mapping_status'] ?? ''));
            $publication_state = sanitize_key((string) ($row['publication_state'] ?? 'published'));
            if ($mapping_state !== '' && !in_array($mapping_state, array('mapped', 'improve_existing_page'), true)) {
                $needs_attention++;
                continue;
            }
            if (in_array($publication_state, array('draft', 'private', 'trash', 'ignored', 'noindex', 'not_indexable', 'mapping_url_unresolved'), true)) {
                $needs_attention++;
            }
        }

        $mapping_needed = $this->aysa_agent8_count_onpage_keyword_mapping_needed($project_id);
        if ($mapping_needed > 0) {
            return array(
                'next_step' => 'keyword_mapping',
                'reason' => 'keyword_mapping_missing',
                'eligible_count' => $mapping_needed,
                'needs_attention_count' => $needs_attention,
                'estimated_credits' => $mapping_needed,
                'will_apply' => false,
                'blocked_reasons' => array(),
            );
        }

        $meta_apply = $this->aysa_agent8_prepare_onpage_meta_apply_payload(array(), $project_id);
        $meta_apply_count = intval($meta_apply['apply_scope_count'] ?? 0);
        if ($meta_apply_count > 0) {
            return array(
                'next_step' => 'meta_apply',
                'reason' => 'safe_generated_meta_ready',
                'eligible_count' => $meta_apply_count,
                'needs_attention_count' => $needs_attention,
                'estimated_credits' => 0,
                'will_apply' => true,
                'blocked_reasons' => array(),
            );
        }

        $meta_generation_selection = array();
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $entity_key = trim((string) ($row['entity_key'] ?? ''));
            $mapping_state = sanitize_key((string) ($row['mapping_state'] ?? $row['mapping_status'] ?? ''));
            $main_keyword = trim((string) ($row['main_keyword'] ?? $row['primary_keyword'] ?? ''));
            $meta_state = sanitize_key((string) ($row['meta_state'] ?? ''));
            $package = $this->aysa_agent8_decode_onpage_package_value($row['meta_package_json'] ?? ($row['meta_package'] ?? array()));
            if ($entity_key === '' || $main_keyword === '' || $mapping_state !== 'mapped') {
                continue;
            }
            if (in_array($meta_state, array('generated', 'applied'), true) || $package !== array()) {
                continue;
            }
            $meta_generation_selection[] = $entity_key;
        }
        $meta_generation = $this->aysa_agent8_prepare_onpage_meta_generation_payload(array(
            'selection' => array_slice(array_values(array_unique($meta_generation_selection)), 0, 10),
            'worker_batch_size' => 10,
        ), $project_id);
        $meta_generation_count = count(isset($meta_generation['selection']) && is_array($meta_generation['selection']) ? $meta_generation['selection'] : array());
        if ($meta_generation_count > 0) {
            return array(
                'next_step' => 'meta_generate',
                'reason' => 'meta_generation_needed',
                'eligible_count' => $meta_generation_count,
                'selection' => array_values(array_slice($meta_generation['selection'], 0, 10)),
                'needs_attention_count' => $needs_attention,
                'estimated_credits' => $meta_generation_count,
                'will_apply' => false,
                'blocked_reasons' => array(),
            );
        }

        $aeo_apply = $this->aysa_agent8_prepare_onpage_aeo_apply_payload(array(), $project_id);
        $aeo_apply_count = intval($aeo_apply['apply_scope_count'] ?? 0);
        if ($aeo_apply_count > 0) {
            return array(
                'next_step' => 'aeo_review',
                'reason' => 'aeo_appendix_preview_ready_apply_blocked',
                'eligible_count' => $aeo_apply_count,
                'needs_attention_count' => $needs_attention,
                'estimated_credits' => 0,
                'will_apply' => false,
                'blocked_reasons' => array('onpage_aeo_appendix_contract_needed'),
            );
        }

        $aeo_generation = $this->aysa_agent8_resolve_onpage_aeo_generation_selection($project_id);
        $aeo_generation_count = count(isset($aeo_generation['selection']) && is_array($aeo_generation['selection']) ? $aeo_generation['selection'] : array());
        if ($aeo_generation_count > 0) {
            return array(
                'next_step' => 'aeo_generate',
                'reason' => 'aeo_generation_needed',
                'eligible_count' => $aeo_generation_count,
                'selection' => array_values(array_slice($aeo_generation['selection'], 0, 10)),
                'needs_attention_count' => $needs_attention,
                'estimated_credits' => $aeo_generation_count,
                'will_apply' => false,
                'blocked_reasons' => array(),
            );
        }

        return array(
            'next_step' => 'report',
            'reason' => 'onpage_report_ready',
            'eligible_count' => count($rows),
            'needs_attention_count' => $needs_attention,
            'estimated_credits' => 0,
            'will_apply' => false,
            'blocked_reasons' => array(),
        );
    }

    private function aysa_agent8_onpage_keyword_mapping_targets($project_id) {
        $project_id = intval($project_id);
        $service = $this->aysa_onpage_workspace_service();
        if ($project_id <= 0 || !$service || !method_exists($service, 'build_run_selection_meta')) {
            return array();
        }

        $contexts = $service->build_run_selection_meta($project_id, 'all', array(), array());
        $targets = array();
        $allowed_groups = array('product', 'ecommerce_category', 'category', 'product_cat', 'page', 'article', 'post');
        $blocked_publication = array('ignored', 'noindex', 'not_indexable', 'draft', 'private', 'trash', 'mapping_url_unresolved');
        foreach ($contexts as $context) {
            if (!is_array($context)) {
                continue;
            }
            $entity_key = trim((string) ($context['entity_key'] ?? ''));
            $entity_id = intval($context['entity_id'] ?? 0);
            $publication_state = sanitize_key((string) ($context['publication_state'] ?? 'published'));
            $entity_group = sanitize_key((string) ($context['entity_group'] ?? $context['post_type'] ?? $context['taxonomy'] ?? ''));
            if ($entity_key === '' || $entity_id <= 0 || in_array($publication_state, $blocked_publication, true)) {
                continue;
            }
            if ($entity_group !== '' && !in_array($entity_group, $allowed_groups, true)) {
                continue;
            }
            $targets[] = array(
                'entity_key' => $entity_key,
                'title' => sanitize_text_field((string) ($context['title'] ?? '')),
                'url' => esc_url_raw((string) ($context['url'] ?? '')),
                'entity_group' => $entity_group,
            );
        }

        return array_values($targets);
    }

    private function aysa_agent8_onpage_keyword_mapping_diagnostics($project_id, $workspace, $associations) {
        $project_id = intval($project_id);
        $workspace = is_array($workspace) ? $workspace : array();
        $associations = is_array($associations) ? $associations : array();
        $clusters = isset($workspace['clusters']) && is_array($workspace['clusters']) ? $workspace['clusters'] : array();
        $canonical = 0;
        $onpage_mapped = 0;
        $resolved = 0;
        $unresolved = array();
        $canonical_urls = array();
        $status_counts = array(
            'mapped' => 0,
            'improve_existing_page' => 0,
            'review' => 0,
            'rejected' => 0,
            'excluded' => 0,
            'unknown' => 0,
        );

        foreach ($clusters as $cluster) {
            if (!is_array($cluster)) {
                continue;
            }
            $mapping_status = sanitize_key((string) ($cluster['mapping_status'] ?? ''));
            $selection_state = sanitize_key((string) ($cluster['selection_state'] ?? ''));
            if ($selection_state === 'excluded') {
                $status_counts['excluded']++;
                continue;
            }
            if ($mapping_status === '') {
                $mapping_status = 'unknown';
            }
            if (!isset($status_counts[$mapping_status])) {
                $status_counts['unknown']++;
            } else {
                $status_counts[$mapping_status]++;
            }
            if (!in_array($mapping_status, array('mapped', 'improve_existing_page', 'review'), true)) {
                continue;
            }
            $target_url = $this->aysa_keyword_mapping_extract_cluster_target_url($cluster);
            if ($target_url === '') {
                continue;
            }
            $keyword_candidates = $this->aysa_keyword_mapping_cluster_keyword_candidates($cluster, $selection_state ?: 'review');
            if (empty($keyword_candidates)) {
                continue;
            }
            $canonical++;
            if (in_array($mapping_status, array('mapped', 'improve_existing_page'), true)) {
                $onpage_mapped++;
            }
            $normalized_url = $this->aysa_keyword_mapping_normalize_url($target_url);
            if ($normalized_url !== '') {
                $canonical_urls[$normalized_url] = true;
            }
            $target = $this->aysa_keyword_mapping_resolve_local_association_target_by_url($target_url);
            if (is_array($target) && !empty($target['associated_on']) && intval($target['id_intern'] ?? 0) > 0) {
                $resolved++;
            } elseif (count($unresolved) < 50) {
                $unresolved[] = $target_url;
            }
        }

        $local_urls = array();
        foreach ($associations as $association) {
            if (!is_array($association)) {
                continue;
            }
            $association_url = (string) ($association['target_url'] ?? $association['link'] ?? '');
            $normalized_url = $this->aysa_keyword_mapping_normalize_url($association_url);
            if ($normalized_url !== '') {
                $local_urls[$normalized_url] = true;
            }
        }
        $stale = 0;
        foreach (array_keys($local_urls) as $local_url) {
            if (!isset($canonical_urls[$local_url])) {
                $stale++;
            }
        }

        return array(
            'project_id' => $project_id,
            'canonical_count' => $canonical,
            'canonical_keyword_mapping_count' => $canonical,
            'legacy_cache_count' => count($clusters),
            'local_associations_count' => count($associations),
            'onpage_mapped_count' => $onpage_mapped,
            'unresolved_url_count' => max(0, $canonical - $resolved),
            'stale_associations_count' => $stale,
            'status_counts' => $status_counts,
            'mapped_clusters' => $canonical,
            'resolved_entities' => $resolved,
            'unresolved_entities' => max(0, $canonical - $resolved),
            'local_associations' => count($associations),
            'unresolved_urls' => $unresolved,
        );
    }

    private function aysa_agent8_runtime_bundle_subset($bundle, $keys) {
        if (!is_array($bundle) || !is_array($keys) || empty($keys)) {
            return array();
        }

        $subset = array();
        foreach ($keys as $key) {
            $key = (string) $key;
            if ($key !== '' && array_key_exists($key, $bundle)) {
                $subset[$key] = $bundle[$key];
            }
        }

        return $subset;
    }

    private function aysa_agent8_get_offpage_gbp_overview_snapshot($project_id, $sync_locations = false) {
        $project_id = intval($project_id);
        $site_host = wp_parse_url(home_url(), PHP_URL_HOST);
        $site_host = is_string($site_host) ? strtolower(trim($site_host)) : '';
        if ($project_id <= 0 || $site_host === '') {
            return array();
        }

        $key = 'aysa_gbp_overview_' . $project_id . '_' . md5($site_host);
        $overview = get_option($key, array());
        $overview = is_array($overview) ? $overview : array();
        if ($sync_locations) {
            $overview = $this->aysa_agent8_refresh_gbp_overview_for_readiness($project_id, $site_host, $overview, $key);
        }
        $overview['agent8_readiness_diagnostics'] = $this->aysa_agent8_build_gbp_readiness_diagnostics($project_id, $site_host, $overview);
        return $overview;
    }

    private function aysa_agent8_refresh_gbp_overview_for_readiness($project_id, $site_host, array $overview, $option_key) {
        $payload = array(
            'project_id' => intval($project_id),
            'site_host' => sanitize_text_field((string) $site_host),
        );
        $fresh = $this->aysa_broker_request('POST', '/v1/offpage/gbp/overview', $payload, 60);
        if (!is_wp_error($fresh) && is_array($fresh)) {
            $overview = $fresh;
            update_option($option_key, $overview, false);
        }

        $connection = isset($overview['connection']) && is_array($overview['connection']) ? $overview['connection'] : array();
        $connected = !empty($connection['connected']) || sanitize_key((string) ($connection['state'] ?? '')) === 'connected';
        $gbp_capability = !empty($connection['capabilities']['gbp']) || sanitize_key((string) ($connection['state'] ?? '')) === 'connected_no_locations';
        $locations = isset($overview['locations']) && is_array($overview['locations']) ? $overview['locations'] : array();
        if ($connected && $gbp_capability && count($locations) <= 0) {
            $sync = $this->aysa_broker_request('POST', '/v1/offpage/gbp/setup-locations/sync', $payload, 90);
            $overview['agent8_location_discovery'] = array(
                'source' => 'setup_locations_sync_read_only',
                'status' => is_wp_error($sync) ? 'failed' : 'completed',
                'error' => is_wp_error($sync) ? sanitize_text_field((string) $sync->get_error_message()) : '',
                'synced_at' => gmdate('c'),
            );
            if (!is_wp_error($sync) && is_array($sync)) {
                $overview['agent8_location_discovery']['accounts_count'] = intval($sync['accounts_count'] ?? 0);
                $overview['agent8_location_discovery']['locations_count'] = intval($sync['locations_count'] ?? 0);
                $fresh_after_sync = $this->aysa_broker_request('POST', '/v1/offpage/gbp/overview', $payload, 60);
                if (!is_wp_error($fresh_after_sync) && is_array($fresh_after_sync)) {
                    $fresh_after_sync['agent8_location_discovery'] = $overview['agent8_location_discovery'];
                    $overview = $fresh_after_sync;
                }
            }
            update_option($option_key, $overview, false);
        }
        return $overview;
    }

    private function aysa_agent8_build_gbp_readiness_diagnostics($project_id, $site_host, array $overview) {
        $oauth_status = array();
        if (class_exists('Aysa_Google_OAuth_Manager')) {
            $oauth_manager = Aysa_Google_OAuth_Manager::get_instance();
            if (is_object($oauth_manager) && method_exists($oauth_manager, 'get_status_for_project')) {
                $status = $oauth_manager->get_status_for_project(intval($project_id));
                if (!is_wp_error($status) && is_array($status)) {
                    $oauth_status = $status;
                }
            }
        }

        $locations = array();
        foreach (array('locations', 'available_locations', 'gbp_locations') as $key) {
            if (isset($overview[$key]) && is_array($overview[$key])) {
                $locations = array_values($overview[$key]);
                break;
            }
        }
        if ($locations === array() && isset($overview['accounts']) && is_array($overview['accounts'])) {
            foreach ($overview['accounts'] as $account) {
                if (is_array($account) && isset($account['locations']) && is_array($account['locations'])) {
                    foreach ($account['locations'] as $location) {
                        if (is_array($location)) {
                            $locations[] = $location;
                        }
                    }
                }
            }
        }

        $primary_location = isset($overview['primary_location']) && is_array($overview['primary_location']) ? $overview['primary_location'] : array();
        $selected_location = '';
        foreach (array('primary_location_name', 'primary_location', 'selected_location_name', 'selected_location', 'location_name') as $key) {
            if (isset($overview[$key]) && is_scalar($overview[$key]) && trim((string) $overview[$key]) !== '') {
                $selected_location = sanitize_text_field((string) $overview[$key]);
                break;
            }
        }
        if ($selected_location === '' && isset($primary_location['title']) && trim((string) $primary_location['title']) !== '') {
            $selected_location = sanitize_text_field((string) $primary_location['title']);
        }
        if ($selected_location === '' && isset($primary_location['location_name']) && trim((string) $primary_location['location_name']) !== '') {
            $selected_location = sanitize_text_field((string) $primary_location['location_name']);
        }
        $selected_location_id = '';
        foreach (array('primary_location_id', 'selected_location_id', 'location_id', 'location_name') as $key) {
            if (isset($overview[$key]) && is_scalar($overview[$key]) && trim((string) $overview[$key]) !== '') {
                $selected_location_id = sanitize_text_field((string) $overview[$key]);
                break;
            }
        }
        if ($selected_location_id === '' && isset($primary_location['location_name']) && trim((string) $primary_location['location_name']) !== '') {
            $selected_location_id = sanitize_text_field((string) $primary_location['location_name']);
        }

        $connection = isset($overview['connection']) && is_array($overview['connection']) ? $overview['connection'] : array();
        $connection_state = sanitize_key((string) ($overview['connection_state'] ?? $overview['status'] ?? $connection['state'] ?? ''));
        $oauth_connected = !empty($oauth_status['connected']);
        if ($connection_state === '') {
            $connection_state = $oauth_connected ? 'connected' : 'unknown';
        }
        $token_exists = $oauth_connected || !empty($overview['token_exists']) || !empty($overview['access_token_present']) || !empty($overview['refresh_token_present']);
        $locations_count = count($locations);
        $selected_location_ready = $selected_location !== '' || $selected_location_id !== '';
        $connection_ready = in_array($connection_state, array('connected', 'authenticated', 'active', 'ready'), true) || $oauth_connected;
        $blockers = array();
        if (!$connection_ready) {
            $blockers[] = 'gbp_auth_missing_or_unknown';
        }
        if ($locations_count <= 0) {
            $blockers[] = 'gbp_locations_not_discovered';
        }
        if (!$selected_location_ready) {
            $blockers[] = 'gbp_selected_location_missing';
        }

        return array(
            'source' => 'plugin_cached_overview_plus_oauth_status',
            'project_id' => intval($project_id),
            'site_host' => sanitize_text_field((string) $site_host),
            'connection_status' => $connection_state,
            'connection_ready' => $connection_ready,
            'token_exists' => (bool) $token_exists,
            'oauth_status_available' => $oauth_status !== array(),
            'account_id' => sanitize_text_field((string) ($overview['account_id'] ?? $overview['primary_account_id'] ?? $primary_location['account_name'] ?? '')),
            'account_name' => sanitize_text_field((string) ($overview['account_name'] ?? $overview['primary_account_name'] ?? $primary_location['account_name'] ?? '')),
            'locations_discovered' => $locations_count,
            'locations' => array_slice($locations, 0, 50),
            'selected_location' => $selected_location,
            'selected_location_id' => $selected_location_id,
            'selected_account_name' => sanitize_text_field((string) ($primary_location['account_name'] ?? '')),
            'selected_at' => sanitize_text_field((string) ($primary_location['selected_at'] ?? '')),
            'selected_by' => sanitize_text_field((string) ($primary_location['selected_by'] ?? '')),
            'selection_context' => sanitize_text_field((string) ($primary_location['selection_context'] ?? '')),
            'selected_location_ready' => $selected_location_ready,
            'permissions_scopes_available' => isset($oauth_status['granted_scopes']) && is_array($oauth_status['granted_scopes']),
            'granted_scopes_count' => isset($oauth_status['granted_scopes']) && is_array($oauth_status['granted_scopes']) ? count($oauth_status['granted_scopes']) : 0,
            'last_sync_at' => sanitize_text_field((string) ($overview['last_sync_at'] ?? $overview['updated_at'] ?? $overview['synced_at'] ?? $primary_location['updated_at'] ?? $overview['agent8_location_discovery']['synced_at'] ?? '')),
            'location_discovery' => isset($overview['agent8_location_discovery']) && is_array($overview['agent8_location_discovery']) ? $overview['agent8_location_discovery'] : array(),
            'readiness_blockers' => $blockers,
            'readiness_impact' => $blockers === array() ? 'can_reach_final_confirmation_required' : 'publish_readiness_blocked',
            'next_safe_step' => $blockers === array()
                ? 'Review final GBP post payload and provide explicit final confirmation only after execution gate is enabled.'
                : 'Connect Google Business Profile, sync locations and select the correct GBP location before publish readiness.',
        );
    }

    private function aysa_agent8_get_offpage_orders_snapshot($project_id) {
        $orders = $this->aysa_get_adverlink_orders($project_id);
        return is_array($orders) ? array_values($orders) : array();
    }

    private function aysa_agent8_get_offpage_link_exchange_profile_snapshot($project_id) {
        $profile = $this->aysa_get_link_exchange_profile($project_id);
        return is_array($profile) ? $profile : array();
    }

    private function aysa_agent8_get_link_exchange_matches_key($project_id) {
        return 'aysa_agent8_link_exchange_matches_' . intval($project_id);
    }

    private function aysa_agent8_get_link_exchange_last_request_key($project_id) {
        return 'aysa_agent8_link_exchange_last_request_' . intval($project_id);
    }

    private function aysa_exchange_auto_activation_status_key($project_id) {
        return 'aysa_exchange_auto_activation_status_' . intval($project_id);
    }

    private function aysa_get_exchange_auto_activation_status($project_id) {
        $status = get_option($this->aysa_exchange_auto_activation_status_key($project_id), array());
        return is_array($status) ? $status : array();
    }

    private function aysa_set_exchange_auto_activation_status($project_id, $exchange_ref, $status, $reason = '') {
        $exchange_ref = sanitize_text_field((string) $exchange_ref);
        if ($exchange_ref === '') {
            return;
        }
        $map = $this->aysa_get_exchange_auto_activation_status($project_id);
        $map[$exchange_ref] = array(
            'status' => sanitize_key((string) $status),
            'reason' => sanitize_key((string) $reason),
            'updated_at' => current_time('mysql'),
        );
        update_option($this->aysa_exchange_auto_activation_status_key($project_id), $map, false);
    }

    private function aysa_agent8_set_link_exchange_autoactivation($project_id, $enabled) {
        update_option('aysa_link_exchange_auto_activation_enabled_' . intval($project_id), $enabled ? '1' : '0', false);
    }

    private function aysa_agent8_get_link_exchange_profile_for_agent8($project_id) {
        $profile = $this->aysa_agent8_compact_link_exchange_profile(
            $this->aysa_agent8_get_offpage_link_exchange_profile_snapshot($project_id)
        );
        $matches = get_option($this->aysa_agent8_get_link_exchange_matches_key($project_id), array());
        if (is_array($matches) && isset($matches['items']) && is_array($matches['items'])) {
            $profile['compatible_portals'] = $this->aysa_agent8_compact_link_exchange_portals($matches['items']);
            $profile['matches_refreshed_at'] = sanitize_text_field((string) ($matches['refreshed_at'] ?? ''));
        }
        return $profile;
    }

    private function aysa_agent8_compact_adverlink_orders(array $orders) {
        return array_slice(array_map(static function ($order) {
            $order = is_array($order) ? $order : array();
            return array(
                'portal_id' => sanitize_text_field((string) ($order['portal_id'] ?? $order['id'] ?? '')),
                'portal_domain' => sanitize_text_field((string) ($order['portal_domain'] ?? $order['domain'] ?? '')),
                'domain' => sanitize_text_field((string) ($order['domain'] ?? $order['portal_domain'] ?? '')),
                'service_type' => sanitize_key((string) ($order['service_type'] ?? '')),
                'status' => sanitize_key((string) ($order['status'] ?? '')),
                'target_url' => esc_url_raw((string) ($order['target_url'] ?? '')),
                'keyword' => sanitize_text_field((string) ($order['keyword'] ?? '')),
                'authority' => floatval($order['authority'] ?? $order['domain_authority'] ?? $order['score'] ?? 0),
                'estimated_cost_cents' => intval($order['estimated_cost_cents'] ?? $order['cost_cents'] ?? $order['price_cents'] ?? 0),
                'included_link_eligible' => !empty($order['included_link_eligible']),
            );
        }, $orders), 0, 100);
    }

    private function aysa_agent8_compact_link_exchange_profile(array $profile) {
        foreach (array('portals', 'compatible_portals') as $key) {
            if (!isset($profile[$key]) || !is_array($profile[$key])) {
                continue;
            }
            $profile[$key] = $this->aysa_agent8_compact_link_exchange_portals($profile[$key]);
        }
        return array_intersect_key($profile, array_flip(array('project_id', 'site_url', 'site_domain', 'website_type', 'industry', 'language', 'country', 'topics', 'authority_value', 'auto_activation_enabled', 'auto_activation_review_hours', 'portals', 'compatible_portals', 'wallet', 'subscription', 'included_links_remaining')));
    }

    private function aysa_agent8_compact_link_exchange_portals(array $portals) {
        return array_slice(array_map(static function ($portal) {
            $portal = is_array($portal) ? $portal : array();
            return array(
                'portal_id' => sanitize_text_field((string) ($portal['portal_id'] ?? $portal['id'] ?? '')),
                'portal_domain' => sanitize_text_field((string) ($portal['portal_domain'] ?? $portal['domain'] ?? $portal['site_domain'] ?? '')),
                'domain' => sanitize_text_field((string) ($portal['domain'] ?? $portal['portal_domain'] ?? $portal['site_domain'] ?? '')),
                'site_url' => esc_url_raw((string) ($portal['site_url'] ?? $portal['url'] ?? '')),
                'site_domain' => sanitize_text_field((string) ($portal['site_domain'] ?? $portal['domain'] ?? $portal['portal_domain'] ?? '')),
                'website_type' => sanitize_text_field((string) ($portal['website_type'] ?? '')),
                'country' => sanitize_text_field((string) ($portal['country'] ?? '')),
                'language' => sanitize_text_field((string) ($portal['language'] ?? '')),
                'authority_bucket' => sanitize_text_field((string) ($portal['authority_bucket'] ?? '')),
                'authority_value' => intval($portal['authority_value'] ?? $portal['authority'] ?? 0),
                'authority' => floatval($portal['authority'] ?? $portal['domain_authority'] ?? $portal['score'] ?? 0),
                'score' => floatval($portal['score'] ?? (($portal['compatibility']['score'] ?? null) ?: 0)),
                'compatibility' => isset($portal['compatibility']) && is_array($portal['compatibility']) ? $portal['compatibility'] : array(),
            );
        }, array_values($portals)), 0, 100);
    }

    private function aysa_agent8_refresh_link_exchange_matches_snapshot($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array();
        }

        $requester = $this->aysa_get_link_exchange_profile($project_id);
        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/portals', array(
            'project_id' => $project_id,
            'per_page' => 100,
            'requester_site_url' => $requester['site_url'],
            'requester_site_domain' => $requester['site_domain'],
            'requester_website_type' => $requester['website_type'],
            'requester_industry' => $requester['industry'],
            'requester_language' => $requester['language'],
            'requester_country' => $requester['country'],
            'requester_topics' => array_values($requester['topics']),
            'requester_authority_value' => intval($requester['authority_value'] ?? 0),
        ), 45);
        if (is_wp_error($broker_result)) {
            return array();
        }

        $items = isset($broker_result['aysa_exchange_sites']) && is_array($broker_result['aysa_exchange_sites'])
            ? $broker_result['aysa_exchange_sites']
            : (isset($broker_result['items']) && is_array($broker_result['items']) ? $broker_result['items'] : array());
        update_option($this->aysa_agent8_get_link_exchange_matches_key($project_id), array(
            'items' => $items,
            'refreshed_at' => current_time('mysql'),
        ), false);
        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_link_exchange_match_sites',
            'offpage_exchange_list_portals_' . $project_id,
            array(
                'module' => 'offpage',
                'source' => 'agent8_v3_aysa_exchange_network',
            ),
            4,
            'agent8v3/find_offpage_link_exchange_matches'
        );

        return $items;
    }

    private function aysa_agent8_link_exchange_anchor_for_target($project_id, $target_url, $fallback = '', array $source = array()) {
        foreach (array('primary_keyword', 'main_keyword', 'target_keyword', 'keyword', 'suggested_anchor_text', 'anchor_text') as $key) {
            $candidate = sanitize_text_field((string) ($source[$key] ?? ''));
            if ($candidate !== '') {
                return $candidate;
            }
        }

        $target_url = untrailingslashit(esc_url_raw((string) $target_url));
        $service = $this->aysa_onpage_workspace_service();
        if ($target_url !== '' && $service && method_exists($service, 'build_run_selection_meta')) {
            $contexts = $service->build_run_selection_meta(intval($project_id), 'all', array(), array());
            foreach ($contexts as $context) {
                if (!is_array($context)) {
                    continue;
                }
                $context_url = untrailingslashit(esc_url_raw((string) ($context['url'] ?? $context['target_url'] ?? $context['permalink'] ?? '')));
                if ($context_url === '' || strcasecmp($context_url, $target_url) !== 0) {
                    continue;
                }
                $keyword = sanitize_text_field((string) ($context['main_keyword'] ?? $context['primary_keyword'] ?? $context['keyword'] ?? ''));
                if ($keyword !== '') {
                    return $keyword;
                }
            }
        }

        return sanitize_text_field((string) $fallback);
    }

    private function aysa_agent8_create_link_exchange_request_from_payload($project_id, array $action_payload) {
        $project_id = intval($project_id);
        $portal = isset($action_payload['counterparty_portal']) && is_array($action_payload['counterparty_portal']) ? $action_payload['counterparty_portal'] : array();
        if ($project_id <= 0 || $portal === array()) {
            return array();
        }

        $requester = $this->aysa_get_link_exchange_profile($project_id);
        $portal_domain = sanitize_text_field((string) ($portal['site_domain'] ?? $portal['domain'] ?? $portal['portal_domain'] ?? ''));
        $requester_target_url = esc_url_raw(trim((string) ($action_payload['requester_target_url'] ?? $requester['site_url'])));
        $counterparty_target_url = esc_url_raw(trim((string) (($action_payload['counterparty_target_url'] ?? '') ?: (($portal['site_url'] ?? '') ?: ($portal_domain !== '' ? 'https://' . $portal_domain . '/' : '')))));
        $requester_anchor_text = sanitize_text_field((string) ($action_payload['requester_anchor_text'] ?? ''));
        if ($requester_anchor_text === '' || strcasecmp($requester_anchor_text, (string) $requester['site_domain']) === 0) {
            $requester_anchor_text = $this->aysa_agent8_link_exchange_anchor_for_target($project_id, $requester_target_url, (string) $requester['site_domain'], $action_payload);
        }
        $counterparty_anchor_text = sanitize_text_field((string) ($action_payload['counterparty_anchor_text'] ?? ''));
        if ($counterparty_anchor_text === '' || strcasecmp($counterparty_anchor_text, $portal_domain) === 0) {
            $counterparty_anchor_text = $this->aysa_agent8_link_exchange_anchor_for_target($project_id, $counterparty_target_url, $portal_domain, $portal);
        }
        $payload = array(
            'project_id' => $project_id,
            'requester_site_url' => $requester['site_url'],
            'requester_site_domain' => $requester['site_domain'],
            'requester_website_type' => $requester['website_type'],
            'requester_industry' => $requester['industry'],
            'requester_language' => $requester['language'],
            'requester_country' => $requester['country'],
            'requester_topics' => array_values($requester['topics']),
            'requester_authority_value' => intval($requester['authority_value'] ?? 0),
            'counterparty_portal' => $portal,
            'requester_target_url' => $requester_target_url,
            'requester_anchor_text' => $requester_anchor_text,
            'counterparty_target_url' => $counterparty_target_url,
            'counterparty_anchor_text' => $counterparty_anchor_text,
            'requester_notes' => sanitize_textarea_field((string) ($action_payload['requester_notes'] ?? 'Agent8 V3 AYSA Link Exchange request.')),
        );

        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/create', $payload, 45);
        if (is_wp_error($broker_result)) {
            return array();
        }

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_link_exchange_create_request',
            'offpage_exchange_create_request_' . md5((string) ($payload['requester_target_url'] ?? '') . '|' . (string) ($payload['counterparty_target_url'] ?? '')),
            array(
                'module' => 'offpage',
                'source' => 'agent8_v3_aysa_exchange_network',
            ),
            12,
            'agent8v3/request_offpage_link_exchange'
        );

        $request = isset($broker_result['request']) && is_array($broker_result['request']) ? $broker_result['request'] : array();
        if ($request !== array() && !empty($request['exchange_ref'])) {
            update_option($this->aysa_agent8_get_link_exchange_last_request_key($project_id), $request, false);
        }

        return $request;
    }

    private function aysa_agent8_get_link_exchange_requests_snapshot($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array();
        }

        $requester = $this->aysa_get_link_exchange_profile($project_id);
        $site_domain = sanitize_text_field((string) ($requester['site_domain'] ?? ''));
        if ($site_domain === '') {
            return array();
        }

        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/list', array(
            'project_id' => $project_id,
            'current_site_domain' => $site_domain,
            'direction' => 'all',
            'status' => '',
            'limit' => 50,
        ), 20);
        $items = array();
        if (!is_wp_error($broker_result) && isset($broker_result['items']) && is_array($broker_result['items'])) {
            $items = $broker_result['items'];
        }

        $last_request = get_option($this->aysa_agent8_get_link_exchange_last_request_key($project_id), array());
        if (is_array($last_request) && !empty($last_request['exchange_ref'])) {
            $last_ref = (string) $last_request['exchange_ref'];
            $has_last = false;
            foreach ($items as $item) {
                if (is_array($item) && (string) ($item['exchange_ref'] ?? '') === $last_ref) {
                    $has_last = true;
                    break;
                }
            }
            if (!$has_last) {
                array_unshift($items, $last_request);
            }
        }

        $auto_status = $this->aysa_get_exchange_auto_activation_status($project_id);
        foreach ($items as $index => $item) {
            if (!is_array($item)) {
                continue;
            }
            $ref = (string) ($item['exchange_ref'] ?? '');
            if ($ref !== '' && isset($auto_status[$ref]) && is_array($auto_status[$ref])) {
                $items[$index]['auto_activation_status'] = $auto_status[$ref];
            }
        }

        return $this->aysa_agent8_compact_link_exchange_requests($items);
    }

    private function aysa_agent8_get_link_exchange_cached_requests_snapshot($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array();
        }
        $last_request = get_option($this->aysa_agent8_get_link_exchange_last_request_key($project_id), array());
        if (!is_array($last_request) || empty($last_request['exchange_ref'])) {
            return array();
        }
        return $this->aysa_agent8_compact_link_exchange_requests(array($last_request));
    }

    private function aysa_agent8_compact_link_exchange_requests(array $requests) {
        return array_slice(array_map(function ($request) {
            $request = is_array($request) ? $request : array();
            $status_labels = isset($request['status_labels']) && is_array($request['status_labels']) ? $request['status_labels'] : array();
            $exchange_ref = sanitize_text_field((string) ($request['exchange_ref'] ?? ''));
            $requester_source_url = esc_url_raw((string) ($request['requester_source_url'] ?? $request['requester_published_url'] ?? ''));
            $requester_published_url = esc_url_raw((string) ($request['requester_published_url'] ?? $requester_source_url));
            $counterparty_source_url = esc_url_raw((string) ($request['counterparty_source_url'] ?? $request['counterparty_published_url'] ?? ''));
            $counterparty_published_url = esc_url_raw((string) ($request['counterparty_published_url'] ?? $counterparty_source_url));
            $counterparty_target_url = esc_url_raw((string) ($request['counterparty_target_url'] ?? ''));
            $counterparty_anchor_text = sanitize_text_field((string) ($request['counterparty_anchor_text'] ?? ''));
            $requester_target_url = esc_url_raw((string) ($request['requester_target_url'] ?? ''));
            $requester_anchor_text = sanitize_text_field((string) ($request['requester_anchor_text'] ?? ''));

            return array(
                'id' => intval($request['id'] ?? 0),
                'exchange_ref' => $exchange_ref,
                'viewer_role' => sanitize_key((string) ($request['viewer_role'] ?? '')),
                'requester_site_domain' => sanitize_text_field((string) ($request['requester_site_domain'] ?? '')),
                'counterparty_site_domain' => sanitize_text_field((string) ($request['counterparty_site_domain'] ?? '')),
                'requester_target_url' => $requester_target_url,
                'requester_anchor_text' => $requester_anchor_text,
                'counterparty_target_url' => $counterparty_target_url,
                'counterparty_anchor_text' => $counterparty_anchor_text,
                'requester_source_url' => $requester_source_url,
                'requester_published_url' => $requester_published_url,
                'counterparty_source_url' => $counterparty_source_url,
                'counterparty_published_url' => $counterparty_published_url,
                'requester_link_evidence' => $this->aysa_agent8_link_exchange_url_evidence_readback(
                    $requester_published_url !== '' ? $requester_published_url : $requester_source_url,
                    $counterparty_target_url,
                    $counterparty_anchor_text,
                    $exchange_ref
                ),
                'counterparty_link_evidence' => $this->aysa_agent8_link_exchange_url_evidence_readback(
                    $counterparty_published_url !== '' ? $counterparty_published_url : $counterparty_source_url,
                    $requester_target_url,
                    $requester_anchor_text,
                    $exchange_ref
                ),
                'status' => sanitize_key((string) ($request['status'] ?? '')),
                'current_obligation' => sanitize_key((string) ($request['current_obligation'] ?? '')),
                'status_labels' => array(
                    'summary' => sanitize_text_field((string) ($status_labels['summary'] ?? '')),
                    'obligation' => sanitize_text_field((string) ($status_labels['obligation'] ?? '')),
                ),
                'auto_activation_status' => isset($request['auto_activation_status']) && is_array($request['auto_activation_status'])
                    ? array(
                        'status' => sanitize_key((string) ($request['auto_activation_status']['status'] ?? '')),
                        'reason' => sanitize_key((string) ($request['auto_activation_status']['reason'] ?? '')),
                        'updated_at' => sanitize_text_field((string) ($request['auto_activation_status']['updated_at'] ?? '')),
                    )
                    : array(),
            );
        }, array_values($requests)), 0, 50);
    }

    private function aysa_agent8_link_exchange_url_evidence_readback($source_url, $destination_url, $anchor_text, $exchange_ref) {
        $source_url = esc_url_raw((string) $source_url);
        $destination_url = esc_url_raw((string) $destination_url);
        $anchor_text = sanitize_text_field((string) $anchor_text);
        $exchange_ref = sanitize_text_field((string) $exchange_ref);
        $checked_at = gmdate('c');

        $empty = array(
            'source_page_url' => $source_url,
            'destination_url' => $destination_url,
            'anchor' => $anchor_text,
            'exchange_ref' => $exchange_ref,
            'found' => false,
            'http_status' => 0,
            'link_count' => 0,
            'anchor_match' => false,
            'ref_match' => false,
            'noindex' => false,
            'last_checked' => $checked_at,
            'observed_exchange_refs' => array(),
        );
        if ($source_url === '' || $destination_url === '' || $anchor_text === '' || $exchange_ref === '') {
            return $empty;
        }

        $response = wp_remote_get($source_url, array(
            'timeout' => 15,
            'redirection' => 3,
            'headers' => array(
                'User-Agent' => 'AYSA-LinkExchange-Evidence/1.0',
                'Cache-Control' => 'no-cache',
            ),
        ));
        if (is_wp_error($response)) {
            return $empty;
        }

        $status = intval(wp_remote_retrieve_response_code($response));
        $body = (string) wp_remote_retrieve_body($response);
        $link_count = 0;
        $anchor_match = false;
        $ref_match = false;
        $verified = false;
        $observed_refs = array();

        if (preg_match_all('/\bdata-aysa-exchange-ref=["\']([^"\']+)["\']/i', $body, $ref_matches)) {
            foreach ((array) ($ref_matches[1] ?? array()) as $ref_value) {
                $ref_value = sanitize_text_field(html_entity_decode((string) $ref_value, ENT_QUOTES | ENT_HTML5, 'UTF-8'));
                if ($ref_value === '') {
                    continue;
                }
                $observed_refs[$ref_value] = intval($observed_refs[$ref_value] ?? 0) + 1;
            }
        }

        if (preg_match_all('/<a\b[^>]*>.*?<\/a>/is', $body, $anchors)) {
            foreach ((array) ($anchors[0] ?? array()) as $anchor_html) {
                $href = '';
                $ref = '';
                if (preg_match('/\bhref=["\']([^"\']+)["\']/i', (string) $anchor_html, $m)) {
                    $href = html_entity_decode((string) $m[1], ENT_QUOTES | ENT_HTML5, 'UTF-8');
                }
                if (!$this->aysa_exchange_urls_match($href, $destination_url)) {
                    continue;
                }

                $link_count++;
                $label = trim(wp_strip_all_tags((string) $anchor_html));
                $this_anchor_match = $this->aysa_exchange_normalize_human_string($label) === $this->aysa_exchange_normalize_human_string($anchor_text);
                if ($this_anchor_match) {
                    $anchor_match = true;
                }
                if (preg_match('/\bdata-aysa-exchange-ref=["\']([^"\']+)["\']/i', (string) $anchor_html, $m)) {
                    $ref = html_entity_decode((string) $m[1], ENT_QUOTES | ENT_HTML5, 'UTF-8');
                }
                $this_ref_match = hash_equals($exchange_ref, sanitize_text_field((string) $ref));
                if ($this_ref_match) {
                    $ref_match = true;
                }
                if ($this_anchor_match && $this_ref_match) {
                    $verified = true;
                }
            }
        }

        $has_noindex = preg_match('/<meta[^>]+name=["\']robots["\'][^>]+content=["\'][^"\']*noindex/i', $body) === 1;

        return array(
            'source_page_url' => $source_url,
            'destination_url' => $destination_url,
            'anchor' => $anchor_text,
            'exchange_ref' => $exchange_ref,
            'found' => $status >= 200 && $status < 300 && $verified && !$has_noindex,
            'http_status' => $status,
            'link_count' => $link_count,
            'anchor_match' => $anchor_match,
            'ref_match' => $ref_match,
            'noindex' => $has_noindex,
            'last_checked' => $checked_at,
            'observed_exchange_refs' => $observed_refs,
        );
    }

    private function aysa_agent8_compact_backlink_strategy_research_snapshot($project_id) {
        $project_id = intval($project_id);
        return array(
            'modules' => array(
                'competitor_backlinks' => $this->aysa_adjustments_get_competitor_backlinks_snapshot($project_id),
                'competitor_gap' => $this->aysa_adjustments_get_competitor_gap_snapshot($project_id),
                'competitor_identification' => $this->aysa_adjustments_get_competitor_identification_snapshot($project_id),
            ),
        );
    }

    private function aysa_agent8_get_offpage_backlink_audit_snapshot($project_id) {
        $snapshot = get_option($this->aysa_get_backlink_audit_key($project_id), array());
        $last_sync = get_option($this->aysa_get_backlink_audit_sync_key($project_id), array());
        return array(
            'snapshot' => is_array($snapshot) ? $snapshot : array(),
            'last_sync' => is_array($last_sync) ? $last_sync : array(),
        );
    }

    private function aysa_agent8_get_offpage_backlink_strategy_snapshot($project_id) {
        $strategy = get_option($this->aysa_get_backlink_strategy_key($project_id), array());
        $last_sync = get_option($this->aysa_get_backlink_strategy_sync_key($project_id), array());
        return array(
            'strategy' => is_array($strategy) ? $strategy : array(),
            'last_sync' => is_array($last_sync) ? $last_sync : array(),
        );
    }

    private function aysa_agent8_compact_offpage_rows($rows, array $allowed_keys, $preserve_keys = false) {
        if (!is_array($rows)) {
            return array();
        }
        $allowed = array_fill_keys($allowed_keys, true);
        $compact = array();
        foreach ($rows as $index => $row) {
            if (!is_array($row)) {
                continue;
            }
            $item = array();
            foreach ($allowed as $key => $_enabled) {
                if (array_key_exists($key, $row)) {
                    if (is_array($row[$key])) {
                        $item[$key] = $key === 'approved_links' ? array_slice($row[$key], 0, 2) : array();
                    } elseif (is_string($row[$key])) {
                        $item[$key] = substr($row[$key], 0, 300);
                    } else {
                        $item[$key] = $row[$key];
                    }
                }
            }
            if ($preserve_keys) {
                $compact[$index] = $item;
            } else {
                $compact[] = $item;
            }
        }
        return $compact;
    }

    private function aysa_agent8_compact_offpage_map($value, array $allowed_keys) {
        if (!is_array($value)) {
            return array();
        }
        $compact = array();
        foreach ($allowed_keys as $key) {
            if (array_key_exists($key, $value) && !is_array($value[$key])) {
                $compact[$key] = $value[$key];
            }
        }
        return $compact;
    }

    private function aysa_agent8_compact_offpage_backlink_strategy_snapshot($snapshot, $page = 1, $per_page = 25, array $filters = array()) {
        if (!is_array($snapshot)) {
            return array();
        }
        $strategy = isset($snapshot['strategy']) && is_array($snapshot['strategy']) ? $snapshot['strategy'] : array();
        $line_keys = array(
            'recommendation_id', 'line_id', 'module', 'target_url', 'target_role', 'keyword', 'anchor',
            'title', 'topic', 'portal_id', 'portal_domain', 'partner', 'source_page',
            'display_url', 'estimated_cost_cents', 'monthly_cost_cents',
            'authority_tier', 'status', 'blocked_reason', 'max_total_cost', 'approval_required',
            'duration_months', 'quota_remaining', 'billing_period', 'approved_links',
            'priority', 'source', 'mapping_source', 'canonical_url', 'indexability', 'http_status',
        );
        $recommendation_keys = array(
            'recommendation_id', 'line_id', 'recommendation_type', 'type', 'recommended_source_type',
            'target_url', 'target_role', 'keyword', 'anchor', 'anchor_suggestion', 'reason',
            'recommendation_reason', 'why', 'priority', 'priority_label', 'title', 'article_title',
            'proposed_article_title', 'topic', 'content_angle', 'brief', 'publication_brief',
            'portal_id', 'portal_domain', 'domain', 'partner', 'source_page', 'display_url',
            'ad_description', 'estimated_cost_cents', 'cost_cents', 'monthly_cost_cents',
            'included_link_eligible', 'authority_tier', 'required_links_count', 'anchor_type',
            'status', 'blocked_reason', 'approval_required', 'duration_months', 'quota_remaining',
            'billing_period', 'mapping_source', 'canonical_url', 'indexability', 'http_status',
        );
        $article_keys = array(
            'recommendation_id', 'line_id', 'title', 'article_title', 'topic', 'brief', 'word_count',
            'minimum_words', 'meets_minimum_words', 'publication_status', 'order_ref', 'homepage_url',
            'target_url', 'anchor', 'status',
        );
        $summary_keys = array(
            'current_referring_domains', 'competitor_average_referring_domains', 'needed_links',
            'verdict', 'target_context', 'source', 'generated_at', 'status',
        );
        $diagnostic_keys = array(
            'valid_targets', 'excluded_utility', 'source', 'status', 'ready_count',
            'needs_onpage_first', 'technical_blocked',
        );
        $cart_keys = array(
            'included_count', 'estimated_extra_cost_cents', 'potential_external_cost_cents',
            'reviewable_external_cost_cents', 'total_known_external_credits',
            'total_potential_external_credits', 'marketplace_test_scope', 'status',
        );
        $manifest_cost_keys = array(
            'total_potential_external_credits', 'total_known_external_credits',
            'total_included_links', 'total_monthly_cost_cents',
        );
        $reduced_strategy = array(
            'backlink_strategy_summary' => $this->aysa_agent8_compact_offpage_map($strategy['backlink_strategy_summary'] ?? array(), $summary_keys),
            'target_diagnostics' => $this->aysa_agent8_compact_offpage_map($strategy['target_diagnostics'] ?? array(), $diagnostic_keys),
            'cart_summary' => $this->aysa_agent8_compact_offpage_map($strategy['cart_summary'] ?? array(), $cart_keys),
            'execution_manifest' => array(
                'bundle_id' => (string) ($strategy['execution_manifest']['bundle_id'] ?? ''),
                'bundle_hash' => (string) ($strategy['execution_manifest']['bundle_hash'] ?? ''),
                'version' => (string) ($strategy['execution_manifest']['version'] ?? ''),
                'selection_model' => (string) ($strategy['execution_manifest']['selection_model'] ?? ''),
                'total_max_cost' => (int) ($strategy['execution_manifest']['total_max_cost'] ?? 0),
                'rollback_available' => !empty($strategy['execution_manifest']['rollback_available']),
                'readback_available' => !empty($strategy['execution_manifest']['readback_available']),
                'counts' => $this->aysa_agent8_compact_offpage_map(
                    $strategy['execution_manifest']['counts'] ?? array(),
                    array('ready_for_review', 'needs_review', 'blocked', 'duplicate', 'unavailable')
                ),
                'costs' => $this->aysa_agent8_compact_offpage_map($strategy['execution_manifest']['costs'] ?? array(), $manifest_cost_keys),
                'lines' => $this->aysa_agent8_compact_offpage_rows(
                    $strategy['execution_manifest']['lines'] ?? array(),
                    $line_keys
                ),
            ),
            'purchase_recommendations' => $this->aysa_agent8_compact_offpage_rows(
                $strategy['purchase_recommendations'] ?? array(),
                $recommendation_keys
            ),
            'adverlink_articles' => $this->aysa_agent8_compact_offpage_rows(
                $strategy['adverlink_articles'] ?? array(),
                $article_keys,
                true
            ),
        );
        if (isset($strategy['cart_preview']) && is_array($strategy['cart_preview'])) {
            $cart_preview = $strategy['cart_preview'];
            $reduced_strategy['cart_preview'] = array(
                'status' => (string) ($cart_preview['status'] ?? ''),
                'generated_at' => (string) ($cart_preview['generated_at'] ?? ''),
                'summary' => $this->aysa_agent8_compact_offpage_map($cart_preview['summary'] ?? array(), $cart_keys),
                'marketplace_meta' => $this->aysa_agent8_compact_offpage_map($cart_preview['marketplace_meta'] ?? array(), array('test_scope', 'catalog_source', 'status')),
                'items' => $this->aysa_agent8_compact_offpage_rows($cart_preview['items'] ?? array(), $recommendation_keys),
            );
        }
        // Keep the request bounded while preserving the real durable total. The
        // modal asks for one page at a time; it must not report 1/1 when the plan
        // contains hundreds of persisted opportunities.
        $page = max(1, (int) $page);
        $per_page = max(5, min(100, (int) $per_page));
        $search = strtolower(trim((string) ($filters['search'] ?? '')));
        $status = strtolower(trim((string) ($filters['status'] ?? '')));
        $module = strtolower(trim((string) ($filters['module'] ?? '')));
        $filter_rows = static function ($rows) use ($search, $status, $module) {
            if (!is_array($rows)) {
                return array();
            }
            return array_values(array_filter($rows, static function ($row) use ($search, $status, $module) {
                if (!is_array($row)) {
                    return false;
                }
                $haystack = strtolower(implode(' ', array_map(static function ($value) {
                    return is_scalar($value) ? (string) $value : '';
                }, array(
                    $row['target_url'] ?? '',
                    $row['keyword'] ?? '',
                    $row['anchor'] ?? $row['anchor_suggestion'] ?? '',
                    $row['title'] ?? $row['article_title'] ?? $row['proposed_article_title'] ?? '',
                    $row['portal_domain'] ?? $row['domain'] ?? '',
                    $row['partner'] ?? '',
                    $row['module'] ?? $row['recommendation_type'] ?? $row['type'] ?? '',
                    $row['status'] ?? $row['execution_status'] ?? '',
                    $row['blocked_reason'] ?? '',
                ))));
                if ($search !== '' && strpos($haystack, $search) === false) {
                    return false;
                }
                if ($module !== '' && $module !== 'all') {
                    $row_module = strtolower((string) ($row['module'] ?? $row['recommendation_type'] ?? $row['type'] ?? ''));
                    if ($row_module !== $module) {
                        return false;
                    }
                }
                if ($status !== '' && $status !== 'all') {
                    $row_status = strtolower((string) ($row['status'] ?? $row['execution_status'] ?? ''));
                    if ($row_status !== $status && strpos($row_status, $status) === false) {
                        return false;
                    }
                }
                return true;
            }));
        };
        $reduced_strategy['execution_manifest']['lines'] = $filter_rows($reduced_strategy['execution_manifest']['lines']);
        $reduced_strategy['purchase_recommendations'] = $filter_rows($reduced_strategy['purchase_recommendations']);
        $source_rows = $reduced_strategy['execution_manifest']['lines'] !== array()
            ? $reduced_strategy['execution_manifest']['lines']
            : $reduced_strategy['purchase_recommendations'];
        $source_total = count($source_rows);
        $source_pages = max(1, (int) ceil($source_total / $per_page));
        $page = min($page, $source_pages);
        $offset = ($page - 1) * $per_page;
        $module_counts = array();
        foreach ($source_rows as $source_row) {
            if (!is_array($source_row)) {
                continue;
            }
            $source_module = strtolower(trim((string) ($source_row['module'] ?? $source_row['recommendation_type'] ?? $source_row['type'] ?? 'other')));
            if ($source_module === '') {
                $source_module = 'other';
            }
            $module_counts[$source_module] = intval($module_counts[$source_module] ?? 0) + 1;
        }
        $reduced_strategy['recommendation_pagination'] = array(
            'page' => $page,
            'per_page' => $per_page,
            'total' => $source_total,
            'pages' => $source_pages,
            'module_counts' => $module_counts,
        );
        $reduced_strategy['execution_manifest']['lines'] = array_slice($reduced_strategy['execution_manifest']['lines'], $offset, $per_page);
        $reduced_strategy['purchase_recommendations'] = array_slice($reduced_strategy['purchase_recommendations'], $offset, $per_page);
        if (isset($reduced_strategy['cart_preview']['items']) && is_array($reduced_strategy['cart_preview']['items'])) {
            $reduced_strategy['cart_preview']['items'] = array_slice($filter_rows($reduced_strategy['cart_preview']['items']), $offset, $per_page);
        }
        foreach (array('source', 'version', 'run_id', 'generated_at', 'status', 'recommendation_count', 'needed_links') as $key) {
            if (array_key_exists($key, $strategy) && !is_array($strategy[$key])) {
                $reduced_strategy[$key] = $strategy[$key];
            }
        }
        return array(
            'strategy' => $reduced_strategy,
            'last_sync' => $this->aysa_agent8_compact_offpage_map($snapshot['last_sync'] ?? array(), array('source', 'run_id', 'status', 'updated_at')),
        );
    }

    private function aysa_agent8_run_offpage_backlink_audit($project_id, array $operation) {
        $project_id = intval($project_id);
        $domain = sanitize_text_field((string) ($operation['domain'] ?? ''));
        if ($domain === '') {
            $home_host = wp_parse_url(home_url(), PHP_URL_HOST);
            $domain = is_string($home_host) ? $home_host : '';
        }
        $domain = preg_replace('/^www\./', '', strtolower(trim($domain)));
        $limit = max(1, min(500, intval($operation['limit'] ?? 200)));
        $result = $this->aysa_adjustments_run_offpage_backlink_audit($project_id, $domain, $limit, true);
        if (is_wp_error($result)) {
            return array(
                'applied' => false,
                'operation' => 'run_offpage_backlink_audit',
                'domain' => $domain,
                'error' => $result->get_error_message(),
                'snapshot' => $this->aysa_agent8_get_offpage_backlink_audit_snapshot($project_id),
            );
        }
        return array(
            'applied' => true,
            'operation' => 'run_offpage_backlink_audit',
            'domain' => $domain,
            'count' => isset($result['items']) && is_array($result['items']) ? count($result['items']) : 0,
            'provider' => sanitize_text_field((string) ($result['provider'] ?? ($result['fallback_source'] ?? ''))),
            'snapshot' => $this->aysa_agent8_get_offpage_backlink_audit_snapshot($project_id),
        );
    }

    private function aysa_agent8_sync_offpage_backlink_audit_snapshot($project_id, array $operation) {
        $project_id = intval($project_id);
        $snapshot = isset($operation['snapshot']) && is_array($operation['snapshot']) ? $operation['snapshot'] : array();
        if ($project_id <= 0 || $snapshot === array()) {
            return array('applied' => false, 'operation' => 'sync_offpage_backlink_audit_snapshot', 'reason' => 'snapshot_required');
        }
        $domain = sanitize_text_field((string) ($snapshot['domain'] ?? ''));
        if ($domain === '') {
            $home_host = wp_parse_url(home_url(), PHP_URL_HOST);
            $domain = is_string($home_host) ? $home_host : '';
        }
        $domain = preg_replace('/^www\./', '', strtolower(trim($domain)));
        $items = isset($snapshot['items']) && is_array($snapshot['items']) ? array_values($snapshot['items']) : array();
        $provider = sanitize_text_field((string) ($snapshot['provider'] ?? 'dataforseo'));
        $stored_snapshot = array(
            'domain' => $domain,
            'provider' => $provider,
            'generated_at' => sanitize_text_field((string) ($snapshot['generated_at'] ?? current_time('mysql'))),
            'count' => count($items),
            'items' => $items,
        );
        update_option($this->aysa_get_backlink_audit_key($project_id), $stored_snapshot, false);
        if ($domain !== '') {
            update_option($this->aysa_get_backlink_audit_domain_key($project_id, $domain), $stored_snapshot, false);
        }
        update_option($this->aysa_get_backlink_audit_sync_key($project_id), array(
            'synced_at' => current_time('mysql'),
            'domain' => $domain,
            'count' => count($items),
            'provider' => $provider,
            'source' => 'agent8_worker',
        ), false);

        return array(
            'applied' => true,
            'operation' => 'sync_offpage_backlink_audit_snapshot',
            'domain' => $domain,
            'count' => count($items),
            'provider' => $provider,
            'snapshot' => $this->aysa_agent8_get_offpage_backlink_audit_snapshot($project_id),
        );
    }

    private function aysa_agent8_sync_offpage_backlink_strategy_snapshot($project_id, array $operation) {
        $project_id = intval($project_id);
        $strategy = isset($operation['strategy']) && is_array($operation['strategy']) ? $operation['strategy'] : array();
        if ($project_id <= 0 || $strategy === array()) {
            return array('applied' => false, 'operation' => 'sync_offpage_backlink_strategy_snapshot', 'reason' => 'strategy_required');
        }
        $existing_snapshot = $this->aysa_agent8_get_offpage_backlink_strategy_snapshot($project_id);
        $existing_strategy = isset($existing_snapshot['strategy']) && is_array($existing_snapshot['strategy']) ? $existing_snapshot['strategy'] : array();
        if (!empty($existing_strategy['adverlink_articles']) && is_array($existing_strategy['adverlink_articles']) && empty($strategy['adverlink_articles'])) {
            $strategy['adverlink_articles'] = $existing_strategy['adverlink_articles'];
        }
        update_option($this->aysa_get_backlink_strategy_key($project_id), $strategy, false);
        update_option($this->aysa_get_backlink_strategy_sync_key($project_id), array(
            'synced_at' => current_time('mysql'),
            'needed_links' => intval($strategy['backlink_strategy_summary']['needed_links'] ?? 0),
            'recommendations' => isset($strategy['purchase_recommendations']) && is_array($strategy['purchase_recommendations']) ? count($strategy['purchase_recommendations']) : 0,
            'source' => 'agent8_worker',
        ), false);

        return array(
            'applied' => true,
            'operation' => 'sync_offpage_backlink_strategy_snapshot',
            'needed_links' => intval($strategy['backlink_strategy_summary']['needed_links'] ?? 0),
            'recommendations' => isset($strategy['purchase_recommendations']) && is_array($strategy['purchase_recommendations']) ? count($strategy['purchase_recommendations']) : 0,
            'snapshot' => $this->aysa_agent8_get_offpage_backlink_strategy_snapshot($project_id),
        );
    }

    private function aysa_agent8_clear_offpage_backlink_strategy_snapshot($project_id, array $operation) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array('applied' => false, 'operation' => 'clear_offpage_backlink_strategy_snapshot', 'reason' => 'project_required');
        }
        delete_option($this->aysa_get_backlink_strategy_key($project_id));
        update_option($this->aysa_get_backlink_strategy_sync_key($project_id), array(
            'synced_at' => current_time('mysql'),
            'run_id' => intval($operation['run_id'] ?? 0),
            'status' => sanitize_key((string) ($operation['status'] ?? 'queued')),
            'source' => 'agent8_worker_queued',
        ), false);
        return array(
            'applied' => true,
            'operation' => 'clear_offpage_backlink_strategy_snapshot',
            'run_id' => intval($operation['run_id'] ?? 0),
            'snapshot' => $this->aysa_agent8_get_offpage_backlink_strategy_snapshot($project_id),
        );
    }

    private function aysa_agent8_sync_offpage_adverlink_article($project_id, array $operation) {
        $project_id = intval($project_id);
        $article = isset($operation['article']) && is_array($operation['article']) ? $operation['article'] : array();
        $recommendation_id = sanitize_text_field((string) ($article['recommendation_id'] ?? ''));
        if ($project_id <= 0 || $recommendation_id === '' || $article === array()) {
            return array('applied' => false, 'operation' => 'sync_offpage_adverlink_article', 'reason' => 'article_required');
        }
        $snapshot = $this->aysa_agent8_get_offpage_backlink_strategy_snapshot($project_id);
        $strategy = isset($snapshot['strategy']) && is_array($snapshot['strategy']) ? $snapshot['strategy'] : array();
        if ($strategy === array()) {
            return array('applied' => false, 'operation' => 'sync_offpage_adverlink_article', 'reason' => 'strategy_required');
        }
        $articles = isset($strategy['adverlink_articles']) && is_array($strategy['adverlink_articles'])
            ? $strategy['adverlink_articles']
            : array();
        $articles[$recommendation_id] = array(
            'recommendation_id' => $recommendation_id,
            'title' => sanitize_text_field((string) ($article['title'] ?? '')),
            'excerpt' => sanitize_textarea_field((string) ($article['excerpt'] ?? '')),
            'body_html' => wp_kses_post((string) ($article['body_html'] ?? '')),
            'body_text' => sanitize_textarea_field((string) ($article['body_text'] ?? '')),
            'word_count' => intval($article['word_count'] ?? 0),
            'minimum_words' => intval($article['minimum_words'] ?? 2000),
            'meets_minimum_words' => !empty($article['meets_minimum_words']),
            'link_placements' => isset($article['link_placements']) && is_array($article['link_placements']) ? $article['link_placements'] : array(),
            'homepage_url' => esc_url_raw((string) ($article['homepage_url'] ?? '')),
            'target_url' => esc_url_raw((string) ($article['target_url'] ?? '')),
            'status' => sanitize_key((string) ($article['status'] ?? 'draft_ready_for_preview')),
            'generated_at' => current_time('mysql'),
        );
        $strategy['adverlink_articles'] = $articles;
        update_option($this->aysa_get_backlink_strategy_key($project_id), $strategy, false);
        update_option($this->aysa_get_backlink_strategy_sync_key($project_id), array(
            'synced_at' => current_time('mysql'),
            'needed_links' => intval($strategy['backlink_strategy_summary']['needed_links'] ?? 0),
            'recommendations' => isset($strategy['purchase_recommendations']) && is_array($strategy['purchase_recommendations']) ? count($strategy['purchase_recommendations']) : 0,
            'adverlink_articles' => count($articles),
            'source' => 'agent8_adverlink_article',
        ), false);

        return array(
            'applied' => true,
            'operation' => 'sync_offpage_adverlink_article',
            'recommendation_id' => $recommendation_id,
            'word_count' => intval($articles[$recommendation_id]['word_count'] ?? 0),
            'snapshot' => $this->aysa_agent8_get_offpage_backlink_strategy_snapshot($project_id),
        );
    }

    private function aysa_agent8_publish_offpage_adverlink_article($project_id, array $operation) {
        $project_id = intval($project_id);
        $recommendation = isset($operation['recommendation']) && is_array($operation['recommendation']) ? $operation['recommendation'] : array();
        $article = isset($operation['article']) && is_array($operation['article']) ? $operation['article'] : array();
        $recommendation_id = sanitize_text_field((string) ($recommendation['recommendation_id'] ?? $article['recommendation_id'] ?? ''));
        $portal_id = sanitize_text_field((string) ($recommendation['portal_id'] ?? ''));
        $portal_domain = sanitize_text_field((string) ($recommendation['portal_domain'] ?? $recommendation['domain'] ?? ''));
        $keyword = sanitize_text_field((string) ($recommendation['keyword'] ?? $recommendation['anchor_suggestion'] ?? ''));
        $target_url = esc_url_raw((string) ($recommendation['target_url'] ?? $article['target_url'] ?? ''));
        $cost_cents = max(0, intval($recommendation['estimated_cost_cents'] ?? $recommendation['cost_cents'] ?? 0));
        $body_html = wp_kses_post((string) ($article['body_html'] ?? ''));
        $word_count = intval($article['word_count'] ?? 0);
        $minimum_words = max(2000, intval($article['minimum_words'] ?? 2000));
        $title = sanitize_text_field((string) ($article['title'] ?? $recommendation['article_title'] ?? $recommendation['proposed_article_title'] ?? ''));

        if ($project_id <= 0 || $recommendation_id === '' || $portal_id === '' || $portal_domain === '' || $keyword === '' || $target_url === '' || $cost_cents <= 0 || $body_html === '' || $word_count < $minimum_words || $title === '') {
            return array(
                'applied' => false,
                'operation' => 'publish_offpage_adverlink_article',
                'reason' => 'publication_payload_not_ready',
            );
        }

        $setup_user_id = $this->aysa_agent8_offpage_purchase_setup_user_id();
        $setup = $this->aysa_get_local_setup_state_for_user($setup_user_id);
        $general = isset($setup['general']) && is_array($setup['general']) ? $setup['general'] : array();
        $business_name = sanitize_text_field((string) ($general['company_name'] ?? get_bloginfo('name')));
        $secondary_keywords = isset($recommendation['secondary_keywords']) && is_array($recommendation['secondary_keywords']) ? $recommendation['secondary_keywords'] : array();

        $order_result = $this->aysa_adjustments_create_adverlink_order_internal($project_id, array(
            'portal_id' => $portal_id,
            'portal_domain' => $portal_domain,
            'service_type' => 'seo_article',
            'billing_type' => 'one_time',
            'keyword' => $keyword,
            'target_url' => $target_url,
            'title' => $title,
            'rel_type' => 'follow',
            'article_topic' => $title,
            'article_brief' => sanitize_textarea_field((string) ($recommendation['article_brief'] ?? $recommendation['editorial_angle'] ?? '')),
            'article_word_count' => $word_count,
            'article_language' => 'ro',
            'article_tone' => 'professional editorial',
            'article_content_html' => $body_html,
            'article_keywords' => array_values(array_unique(array_filter(array_merge(array($keyword), array_map('sanitize_text_field', $secondary_keywords))))),
            'estimated_credits' => $cost_cents,
            'client_name' => $business_name,
            'project_ref' => 'wp-project-' . intval($project_id),
            'order_meta' => array(
                'source' => 'agent8_link_building_plan',
                'placement_type' => 'seo_article',
                'recommendation_id' => $recommendation_id,
            ),
        ));

        if (is_wp_error($order_result)) {
            if ($order_result->get_error_code() === 'insufficient_credits') {
                $error_data = $order_result->get_error_data();
                $required_credits = is_array($error_data) ? intval($error_data['required_credits'] ?? $cost_cents) : $cost_cents;
                $available_credits = is_array($error_data) ? intval($error_data['available_credits'] ?? 0) : 0;
                $payment_response = $this->aysa_build_insufficient_credits_payment_response(
                    $project_id,
                    $required_credits,
                    $available_credits,
                    'ro',
                    'seo_article_order',
                    'offpage_link_building',
                    array(
                        'portal_id' => $portal_id,
                        'portal_domain' => $portal_domain,
                        'recommendation_id' => $recommendation_id,
                        'target_url' => $target_url,
                    )
                );

                return array_merge($payment_response, array(
                    'applied' => false,
                    'operation' => 'publish_offpage_adverlink_article',
                    'reason' => 'payment_required',
                    'message' => $order_result->get_error_message(),
                    'summary' => array(
                        'status' => 'payment_required',
                        'portal_domain' => $portal_domain,
                        'recommendation_id' => $recommendation_id,
                        'required_credits' => intval($payment_response['required_credits'] ?? $required_credits),
                        'available_credits' => intval($payment_response['available_credits'] ?? $available_credits),
                        'missing_credits' => intval($payment_response['missing_credits'] ?? max(0, $required_credits - $available_credits)),
                        'payment_link' => esc_url_raw((string) ($payment_response['payment_link'] ?? '')),
                    ),
                    'projection' => array(
                        'type' => 'offpage_adverlink_article',
                        'title' => 'Adverlink Article Publication',
                        'summary' => array(
                            'status' => 'payment_required',
                            'portal_domain' => $portal_domain,
                            'recommendation_id' => $recommendation_id,
                        ),
                    ),
                ));
            }

            return array(
                'applied' => false,
                'operation' => 'publish_offpage_adverlink_article',
                'reason' => $order_result->get_error_code(),
                'message' => $order_result->get_error_message(),
            );
        }

        $snapshot = $this->aysa_agent8_get_offpage_backlink_strategy_snapshot($project_id);
        $strategy = isset($snapshot['strategy']) && is_array($snapshot['strategy']) ? $snapshot['strategy'] : array();
        $articles = isset($strategy['adverlink_articles']) && is_array($strategy['adverlink_articles']) ? $strategy['adverlink_articles'] : array();
        if (isset($articles[$recommendation_id]) && is_array($articles[$recommendation_id])) {
            $articles[$recommendation_id]['publication_status'] = 'order_submitted';
            $articles[$recommendation_id]['order_ref'] = sanitize_text_field((string) ($order_result['order_ref'] ?? ''));
            $articles[$recommendation_id]['portal_domain'] = $portal_domain;
            $articles[$recommendation_id]['published_to_adverlink_at'] = current_time('mysql');
            $strategy['adverlink_articles'] = $articles;
            update_option($this->aysa_get_backlink_strategy_key($project_id), $strategy, false);
        }

        return array(
            'applied' => true,
            'operation' => 'publish_offpage_adverlink_article',
            'recommendation_id' => $recommendation_id,
            'order_ref' => sanitize_text_field((string) ($order_result['order_ref'] ?? '')),
            'portal_domain' => $portal_domain,
            'charged_credits_cents' => intval($order_result['charged_credits_cents'] ?? 0),
            'included_link_applied' => !empty($order_result['included_link_applied']),
            'snapshot' => $this->aysa_agent8_get_offpage_backlink_strategy_snapshot($project_id),
        );
    }

    private function aysa_agent8_queue_offpage_publisher_article_order($project_id, array $operation) {
        $project_id = intval($project_id);
        $recommendation = isset($operation['recommendation']) && is_array($operation['recommendation']) ? $operation['recommendation'] : array();
        $article = isset($operation['article']) && is_array($operation['article']) ? $operation['article'] : array();
        $recommendation_id = sanitize_text_field((string) ($recommendation['recommendation_id'] ?? $article['recommendation_id'] ?? ''));
        $portal_id = sanitize_text_field((string) ($recommendation['portal_id'] ?? ''));
        $portal_domain = sanitize_text_field((string) ($recommendation['portal_domain'] ?? $recommendation['domain'] ?? ''));
        $keyword = sanitize_text_field((string) ($recommendation['keyword'] ?? $recommendation['anchor_suggestion'] ?? ''));
        $target_url = esc_url_raw((string) ($recommendation['target_url'] ?? $article['target_url'] ?? ''));
        $cost_cents = max(0, intval($recommendation['estimated_cost_cents'] ?? $recommendation['cost_cents'] ?? $operation['max_total_cost'] ?? 0));
        $body_html = wp_kses_post((string) ($article['body_html'] ?? ''));
        $word_count = intval($article['word_count'] ?? 0);
        $minimum_words = max(2000, intval($article['minimum_words'] ?? 2000));
        $title = sanitize_text_field((string) ($article['title'] ?? $recommendation['article_title'] ?? $recommendation['proposed_article_title'] ?? ''));

        if ($project_id <= 0 || $recommendation_id === '' || $portal_id === '' || $portal_domain === '' || $keyword === '' || $target_url === '' || $cost_cents <= 0 || $body_html === '' || $word_count < $minimum_words || $title === '') {
            return array(
                'applied' => false,
                'operation' => 'queue_offpage_publisher_article_order',
                'reason' => 'publisher_article_order_payload_not_ready',
            );
        }

        foreach ($this->aysa_get_adverlink_orders($project_id) as $order) {
            if (!is_array($order)) {
                continue;
            }
            $meta = isset($order['order_meta']) && is_array($order['order_meta']) ? $order['order_meta'] : array();
            if (
                sanitize_key((string) ($order['service_type'] ?? '')) === 'seo_article'
                && sanitize_text_field((string) ($order['portal_id'] ?? '')) === $portal_id
                && esc_url_raw((string) ($order['target_url'] ?? '')) === $target_url
                && sanitize_text_field((string) ($meta['recommendation_id'] ?? '')) === $recommendation_id
                && !in_array(strtoupper((string) ($order['status'] ?? '')), array('CANCELLED', 'AUTO_DISPUTED'), true)
            ) {
                return array(
                    'applied' => true,
                    'operation' => 'queue_offpage_publisher_article_order',
                    'summary' => array(
                        'status' => 'already_queued',
                        'order_ref' => sanitize_text_field((string) ($order['order_ref'] ?? '')),
                        'portal_domain' => $portal_domain,
                        'billing_status' => 'no_new_debit_idempotent_replay',
                    ),
                    'snapshot' => $this->aysa_agent8_get_offpage_orders_snapshot($project_id),
                );
            }
        }

        $setup_user_id = $this->aysa_agent8_offpage_purchase_setup_user_id();
        $setup = $this->aysa_get_local_setup_state_for_user($setup_user_id);
        $general = isset($setup['general']) && is_array($setup['general']) ? $setup['general'] : array();
        $business_name = sanitize_text_field((string) ($general['company_name'] ?? get_bloginfo('name')));
        $secondary_keywords = isset($recommendation['secondary_keywords']) && is_array($recommendation['secondary_keywords']) ? $recommendation['secondary_keywords'] : array();

        $order_result = $this->aysa_adjustments_create_adverlink_order_internal($project_id, array(
            'portal_id' => $portal_id,
            'portal_domain' => $portal_domain,
            'service_type' => 'seo_article',
            'billing_type' => 'one_time',
            'keyword' => $keyword,
            'target_url' => $target_url,
            'title' => $title,
            'rel_type' => 'follow',
            'article_topic' => $title,
            'article_brief' => sanitize_textarea_field((string) ($recommendation['article_brief'] ?? $recommendation['editorial_angle'] ?? '')),
            'article_word_count' => $word_count,
            'article_language' => 'ro',
            'article_tone' => 'professional editorial',
            'article_content_html' => $body_html,
            'article_keywords' => array_values(array_unique(array_filter(array_merge(array($keyword), array_map('sanitize_text_field', $secondary_keywords))))),
            'estimated_credits' => $cost_cents,
            'client_name' => $business_name,
            'project_ref' => 'wp-project-' . intval($project_id),
            'defer_placement_billing_until_public_readback' => true,
            'order_meta' => array(
                'source' => 'agent8_publisher_article_pending_order',
                'placement_type' => 'seo_article',
                'recommendation_id' => $recommendation_id,
                'placement_billing_policy' => 'after_publisher_publish_and_public_readback',
            ),
        ));

        if (is_wp_error($order_result)) {
            return array(
                'applied' => false,
                'operation' => 'queue_offpage_publisher_article_order',
                'reason' => $order_result->get_error_code(),
                'message' => $order_result->get_error_message(),
            );
        }

        return array(
            'applied' => true,
            'operation' => 'queue_offpage_publisher_article_order',
            'summary' => array(
                'status' => 'queued_for_publisher_manual_publish',
                'order_ref' => sanitize_text_field((string) ($order_result['order_ref'] ?? '')),
                'portal_domain' => $portal_domain,
                'charged_credits_cents' => intval($order_result['charged_credits_cents'] ?? 0),
                'provider_cost_cents' => intval($order_result['provider_cost_cents'] ?? 0),
                'billing_status' => 'placement_billing_deferred_until_public_readback',
            ),
            'snapshot' => $this->aysa_agent8_get_offpage_orders_snapshot($project_id),
        );
    }

    private function aysa_agent8_preview_offpage_link_building_cart($project_id, array $operation) {
        $project_id = intval($project_id);
        $snapshot = $this->aysa_agent8_get_offpage_backlink_strategy_snapshot($project_id);
        $strategy = isset($snapshot['strategy']) && is_array($snapshot['strategy']) ? $snapshot['strategy'] : array();
        $recommendations = isset($operation['selected_recommendations']) && is_array($operation['selected_recommendations'])
            ? array_values($operation['selected_recommendations'])
            : array();
        if ($recommendations === array()) {
            $recommendations = isset($strategy['purchase_recommendations']) && is_array($strategy['purchase_recommendations'])
                ? array_values($strategy['purchase_recommendations'])
                : array();
        }
        if ($project_id <= 0 || $strategy === array() || $recommendations === array()) {
            return array('applied' => false, 'operation' => 'preview_offpage_link_building_cart', 'reason' => 'strategy_recommendations_required');
        }

        $portals = $this->aysa_adjustments_list_adverlink_portals_internal($project_id, 'link_insertion', 'one_time', 100, true);
        if (is_wp_error($portals)) {
            return array(
                'applied' => false,
                'operation' => 'preview_offpage_link_building_cart',
                'reason' => sanitize_key((string) $portals->get_error_code()),
                'message' => $portals->get_error_message(),
                'snapshot' => $this->aysa_agent8_get_offpage_backlink_strategy_snapshot($project_id),
            );
        }

        $items = isset($portals['items']) && is_array($portals['items']) ? array_values($portals['items']) : array();
        $marketplace_meta = isset($portals['meta']) && is_array($portals['meta']) ? $portals['meta'] : array();
        $marketplace_test_scope = !empty($marketplace_meta['test_scope']);
        if ($marketplace_test_scope) {
            $items = array();
        }
        $setup_user_id = $this->aysa_agent8_offpage_purchase_setup_user_id();
        $setup_state = $this->aysa_get_local_setup_state_for_user($setup_user_id);
        $shortlist = $this->aysa_adjustments_build_portal_shortlist($items, is_array($setup_state) ? $setup_state : array(), 'link_insertion', 'one_time', max(10, count($recommendations) * 2));
        $shortlist = array_values(array_filter($shortlist, function ($portal) {
            if (!is_array($portal)) {
                return false;
            }
            return !$this->aysa_agent8_is_local_adverlink_domain((string) ($portal['domain'] ?? $portal['portal_domain'] ?? ''));
        }));
        $wallet_snapshot = $this->aysa_build_agent_wallet_snapshot($project_id);
        $included_remaining = max(0, intval($wallet_snapshot['included_links_remaining'] ?? 0));
        $cart_items = array();
        $used_domains = array();

        foreach (array_slice($recommendations, 0, 10) as $recommendation) {
            if (!is_array($recommendation)) {
                continue;
            }
            $article_title = sanitize_text_field((string) ($recommendation['article_title'] ?? $recommendation['proposed_article_title'] ?? ''));
            $target_url = esc_url_raw((string) ($recommendation['target_url'] ?? ''));
            $keyword = sanitize_text_field((string) ($recommendation['keyword'] ?? $recommendation['anchor_suggestion'] ?? ''));
            if ($article_title === '' || $target_url === '' || $keyword === '') {
                continue;
            }
            $portal = $this->aysa_agent8_select_link_building_portal($shortlist, $used_domains, sanitize_key((string) ($recommendation['authority_tier'] ?? '')));
            if ($portal === array()) {
                continue;
            }
            $domain = strtolower((string) ($portal['domain'] ?? $portal['portal_domain'] ?? ''));
            if ($domain !== '') {
                $used_domains[$domain] = true;
            }
            $payload = $this->aysa_agent8_normalize_offpage_purchase_payload(array_merge($portal, array(
                'target_url' => $target_url,
                'keyword' => $keyword,
                'included_link_eligible' => $included_remaining > 0,
            )), 'link_insertion', $keyword);
            if (empty($payload['portal_id']) || empty($payload['portal_domain']) || (empty($payload['included_link_eligible']) && intval($payload['estimated_cost_cents'] ?? 0) <= 0)) {
                continue;
            }
            if (!empty($payload['included_link_eligible'])) {
                $included_remaining--;
            }
            $cart_items[] = array_merge($payload, array(
                'recommendation_id' => sanitize_text_field((string) ($recommendation['recommendation_id'] ?? '')),
                'authority_tier' => sanitize_key((string) ($recommendation['authority_tier'] ?? '')),
                'anchor_type' => sanitize_key((string) ($recommendation['anchor_type'] ?? '')),
                'anchor_suggestion' => sanitize_text_field((string) ($recommendation['anchor_suggestion'] ?? $keyword)),
                'article_title' => $article_title,
                'proposed_article_title' => $article_title,
                'content_angle' => sanitize_text_field((string) ($recommendation['content_angle'] ?? '')),
                'publication_brief' => sanitize_textarea_field((string) ($recommendation['publication_brief'] ?? '')),
                'required_links_count' => max(1, intval($recommendation['required_links_count'] ?? 1)),
                'status' => 'candidate_ready',
            ));
        }

        $included_count = count(array_filter($cart_items, static fn($item) => !empty($item['included_link_eligible'])));
        $extra_cost = array_sum(array_map(static function ($item) {
            return !empty($item['included_link_eligible']) ? 0 : max(0, intval($item['estimated_cost_cents'] ?? 0));
        }, $cart_items));
        $cart_preview = array(
            'generated_at' => current_time('mysql'),
            'source' => 'adverlink_marketplace',
            'items' => $cart_items,
            'summary' => array(
                'status' => 'cart_preview_ready',
                'selected_count' => count($cart_items),
                'included_count' => $included_count,
                'estimated_extra_cost_cents' => $extra_cost,
                'requires_approval' => true,
                'marketplace_test_scope' => $marketplace_test_scope,
            ),
            'marketplace_meta' => $marketplace_meta,
        );
        $strategy['cart_preview'] = $cart_preview;
        $strategy['cart_summary'] = array_merge(
            isset($strategy['cart_summary']) && is_array($strategy['cart_summary']) ? $strategy['cart_summary'] : array(),
            $cart_preview['summary']
        );
        update_option($this->aysa_get_backlink_strategy_key($project_id), $strategy, false);
        update_option($this->aysa_get_backlink_strategy_sync_key($project_id), array(
            'synced_at' => current_time('mysql'),
            'needed_links' => intval($strategy['backlink_strategy_summary']['needed_links'] ?? 0),
            'recommendations' => isset($strategy['purchase_recommendations']) && is_array($strategy['purchase_recommendations']) ? count($strategy['purchase_recommendations']) : 0,
            'cart_items' => count($cart_items),
            'source' => 'agent8_adverlink_cart_preview',
        ), false);

        return array(
            'applied' => true,
            'operation' => 'preview_offpage_link_building_cart',
            'summary' => $cart_preview['summary'],
            'snapshot' => $this->aysa_agent8_get_offpage_backlink_strategy_snapshot($project_id),
        );
    }

    private function aysa_agent8_select_link_building_portal(array $shortlist, array $used_domains, $authority_tier) {
        $min_authority = match (sanitize_key((string) $authority_tier)) {
            'high' => 50,
            'medium' => 30,
            default => 10,
        };
        $fallback = array();
        foreach ($shortlist as $portal) {
            if (!is_array($portal)) {
                continue;
            }
            $domain = strtolower((string) ($portal['domain'] ?? $portal['portal_domain'] ?? ''));
            if ($domain === '' || isset($used_domains[$domain]) || $this->aysa_agent8_is_local_adverlink_domain($domain)) {
                continue;
            }
            if ($fallback === array()) {
                $fallback = $portal;
            }
            if (floatval($portal['authority'] ?? 0) >= $min_authority) {
                return $portal;
            }
        }
        return $fallback;
    }

    private function aysa_agent8_is_local_adverlink_domain($domain) {
        $domain = strtolower(trim((string) $domain));
        return $domain === 'localhost'
            || $domain === '127.0.0.1'
            || $domain === '::1'
            || strpos($domain, 'localhost') !== false;
    }

    private function aysa_agent8_get_offpage_draft_articles_snapshot($project_id) {
        $orders = $this->aysa_get_adverlink_orders($project_id);
        $items = array();
        foreach ((array) $orders as $order) {
            if (!is_array($order)) {
                continue;
            }
            if (sanitize_key((string) ($order['service_type'] ?? '')) !== 'seo_article') {
                continue;
            }
            $draft_id = intval($order['publisher_article_draft_id'] ?? 0);
            if ($draft_id <= 0) {
                continue;
            }
            $status = strtoupper(trim((string) ($order['status'] ?? '')));
            $draft_status = strtoupper(trim((string) ($order['publisher_article_draft_status'] ?? '')));
            if ($draft_status !== 'APPROVED' && $this->aysa_agent8_is_publisher_draft_approved($project_id, $draft_id)) {
                $draft_status = 'APPROVED';
            }
            if ($draft_status === 'APPROVED') {
                $status = 'APPROVED';
            }
            if (in_array($status, array('COMPLETED', 'PUBLISHED', 'LIVE'), true)) {
                continue;
            }
            $items[] = array(
                'draft_id' => $draft_id,
                'order_ref' => sanitize_text_field((string) ($order['order_ref'] ?? '')),
                'portal_domain' => sanitize_text_field((string) ($order['portal_domain'] ?? '')),
                'status' => $status,
                'draft_status' => $draft_status,
                'target_url' => esc_url_raw((string) ($order['target_url'] ?? '')),
                'created_at' => sanitize_text_field((string) ($order['created_at'] ?? '')),
            );
        }
        return array('items' => array_values($items));
    }

    private function aysa_agent8_get_offpage_published_articles_snapshot($project_id) {
        $orders = $this->aysa_get_adverlink_orders($project_id);
        $items = array();
        foreach ((array) $orders as $order) {
            if (!is_array($order)) {
                continue;
            }
            if (sanitize_key((string) ($order['service_type'] ?? '')) !== 'seo_article') {
                continue;
            }
            $status = strtoupper(trim((string) ($order['status'] ?? '')));
            if (!in_array($status, array('COMPLETED', 'PUBLISHED', 'LIVE'), true)) {
                continue;
            }
            $published_url = '';
            $source = null;
            if (isset($order['raw_track']) && is_array($order['raw_track'])) {
                $source = $order['raw_track'];
            } elseif (isset($order['raw_response']) && is_array($order['raw_response'])) {
                $source = $order['raw_response'];
            }
            if (is_array($source)) {
                $published_url = esc_url_raw((string) $this->aysa_find_first_string_by_keys_recursive($source, array(
                    'published_url', 'publishedUrl', 'insertion_url', 'insertionUrl',
                    'article_url', 'articleUrl', 'placement_url', 'placementUrl',
                    'source_url', 'sourceUrl', 'published_on_url', 'publishedOnUrl'
                )));
            }
            $items[] = array(
                'order_ref' => sanitize_text_field((string) ($order['order_ref'] ?? '')),
                'portal_domain' => sanitize_text_field((string) ($order['portal_domain'] ?? '')),
                'status' => $status,
                'published_url' => $published_url,
                'target_url' => esc_url_raw((string) ($order['target_url'] ?? '')),
                'draft_id' => intval($order['publisher_article_draft_id'] ?? 0),
            );
        }
        return array('items' => array_values($items));
    }

    private function aysa_gbp_social_calendar_table() {
        global $wpdb;
        return $wpdb->prefix . 'aysa_gbp_social_calendar';
    }

    private function aysa_gbp_social_calendar_ensure_table() {
        global $wpdb;
        $table = esc_sql($this->aysa_gbp_social_calendar_table());
        $charset_collate = $wpdb->get_charset_collate();
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta("CREATE TABLE {$table} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            project_id bigint(20) unsigned NOT NULL,
            run_id bigint(20) unsigned NOT NULL DEFAULT 0,
            source_type varchar(40) NOT NULL DEFAULT '',
            source_id bigint(20) unsigned NOT NULL DEFAULT 0,
            source_url text NULL,
            source_title text NULL,
            scheduled_for datetime NULL,
            review_deadline_at datetime NULL,
            gbp_summary text NULL,
            cta_type varchar(60) NOT NULL DEFAULT '',
            image_url text NULL,
            primary_keyword varchar(255) NOT NULL DEFAULT '',
            status varchar(40) NOT NULL DEFAULT 'scheduled',
            idempotency_key varchar(191) NOT NULL DEFAULT '',
            content_hash varchar(64) NOT NULL DEFAULT '',
            gbp_result_json longtext NULL,
            last_error text NULL,
            published_at datetime NULL,
            created_at datetime NOT NULL,
            updated_at datetime NOT NULL,
            PRIMARY KEY  (id),
            UNIQUE KEY idempotency_key (idempotency_key),
            KEY project_status (project_id,status),
            KEY due_posts (status,scheduled_for,review_deadline_at)
        ) {$charset_collate};");
    }

    private function aysa_agent8_get_gbp_social_calendar_snapshot($project_id) {
        global $wpdb;
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array('items' => array(), 'counts' => array(), 'next_post' => null);
        }
        $this->aysa_gbp_social_calendar_ensure_table();
        $table = esc_sql($this->aysa_gbp_social_calendar_table());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $rows = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT * FROM {$table} WHERE project_id = %d ORDER BY scheduled_for ASC, id ASC LIMIT 90",
            $project_id
        ), ARRAY_A);
        $items = array();
        $counts = array('generated' => 0, 'scheduled' => 0, 'rejected' => 0, 'published' => 0, 'failed' => 0, 'publishing' => 0);
        foreach ((array) $rows as $row) {
            $status = sanitize_key((string) ($row['status'] ?? 'scheduled'));
            if (!isset($counts[$status])) {
                $counts[$status] = 0;
            }
            $counts[$status]++;
            $counts['generated']++;
            $items[] = array(
                'id' => intval($row['id'] ?? 0),
                'source_type' => sanitize_key((string) ($row['source_type'] ?? '')),
                'source_id' => intval($row['source_id'] ?? 0),
                'source_url' => esc_url_raw((string) ($row['source_url'] ?? '')),
                'source_title' => sanitize_text_field((string) ($row['source_title'] ?? '')),
                'scheduled_for' => sanitize_text_field((string) ($row['scheduled_for'] ?? '')),
                'review_deadline_at' => sanitize_text_field((string) ($row['review_deadline_at'] ?? '')),
                'gbp_summary' => sanitize_textarea_field((string) ($row['gbp_summary'] ?? '')),
                'cta_type' => sanitize_key((string) ($row['cta_type'] ?? '')),
                'image_url' => esc_url_raw((string) ($row['image_url'] ?? '')),
                'primary_keyword' => sanitize_text_field((string) ($row['primary_keyword'] ?? '')),
                'status' => $status,
                'published_at' => sanitize_text_field((string) ($row['published_at'] ?? '')),
                'last_error' => sanitize_text_field((string) ($row['last_error'] ?? '')),
            );
        }
        $next = null;
        foreach ($items as $item) {
            if (in_array((string) ($item['status'] ?? ''), array('scheduled', 'retry'), true)) {
                $next = $item;
                break;
            }
        }
        return array('items' => $items, 'counts' => $counts, 'next_post' => $next);
    }

    private function aysa_agent8_get_gbp_social_sources($project_id) {
        $project_id = intval($project_id);
        $sources = array();
        $add_post_sources = function($post_type, $source_type, $limit) use (&$sources) {
            $query = new WP_Query(array(
                'post_type' => $post_type,
                'post_status' => 'publish',
                'posts_per_page' => $limit,
                'orderby' => 'date',
                'order' => 'DESC',
                'no_found_rows' => true,
                'ignore_sticky_posts' => true,
            ));
            foreach ((array) $query->posts as $post) {
                $url = get_permalink($post);
                if (!$url) {
                    continue;
                }
                $sources[] = array(
                    'source_type' => $source_type,
                    'source_id' => intval($post->ID),
                    'source_url' => esc_url_raw($url),
                    'source_title' => get_the_title($post),
                    'image_url' => get_the_post_thumbnail_url($post, 'large') ?: '',
                    'primary_keyword' => sanitize_text_field((string) get_post_meta($post->ID, '_aysa_primary_keyword', true)),
                );
            }
        };
        if (post_type_exists('product')) {
            $add_post_sources('product', 'product', 60);
        }
        $add_post_sources('page', 'page', 30);
        $add_post_sources('post', 'article', 30);
        if (taxonomy_exists('product_cat')) {
            $terms = get_terms(array('taxonomy' => 'product_cat', 'hide_empty' => true, 'number' => 60));
            foreach ((array) $terms as $term) {
                if (!is_object($term) || empty($term->term_id)) {
                    continue;
                }
                $url = get_term_link($term);
                if (is_wp_error($url)) {
                    continue;
                }
                $thumb_id = intval(get_term_meta((int) $term->term_id, 'thumbnail_id', true));
                $image_url = $thumb_id > 0 ? wp_get_attachment_image_url($thumb_id, 'large') : '';
                $sources[] = array(
                    'source_type' => 'category',
                    'source_id' => intval($term->term_id),
                    'source_url' => esc_url_raw((string) $url),
                    'source_title' => sanitize_text_field((string) $term->name),
                    'image_url' => $image_url ? esc_url_raw($image_url) : '',
                    'primary_keyword' => sanitize_text_field((string) get_term_meta((int) $term->term_id, '_aysa_primary_keyword', true)),
                );
            }
        }
        return array_values(array_slice($sources, 0, 150));
    }

    private function aysa_agent8_sync_gbp_social_calendar($project_id, array $operation) {
        global $wpdb;
        $project_id = intval($project_id);
        $drafts = isset($operation['drafts']) && is_array($operation['drafts']) ? array_values($operation['drafts']) : array();
        if ($project_id <= 0 || $drafts === array()) {
            return array('applied' => false, 'reason' => 'social_calendar_drafts_required');
        }
        $this->aysa_gbp_social_calendar_ensure_table();
        $table = esc_sql($this->aysa_gbp_social_calendar_table());
        $now = current_time('mysql');
        $inserted = 0;
        $updated = 0;
        foreach ($drafts as $draft) {
            if (!is_array($draft)) {
                continue;
            }
            $key = sanitize_text_field((string) ($draft['idempotency_key'] ?? ''));
            if ($key === '') {
                $key = hash('sha256', $project_id . '|' . ($draft['source_type'] ?? '') . '|' . ($draft['source_id'] ?? '') . '|' . ($draft['scheduled_for'] ?? ''));
            }
            $data = array(
                'project_id' => $project_id,
                'run_id' => intval($operation['run_id'] ?? 0),
                'source_type' => sanitize_key((string) ($draft['source_type'] ?? '')),
                'source_id' => intval($draft['source_id'] ?? 0),
                'source_url' => esc_url_raw((string) ($draft['source_url'] ?? '')),
                'source_title' => sanitize_text_field((string) ($draft['source_title'] ?? '')),
                'scheduled_for' => sanitize_text_field((string) ($draft['scheduled_for'] ?? '')),
                'review_deadline_at' => sanitize_text_field((string) ($draft['review_deadline_at'] ?? '')),
                'gbp_summary' => sanitize_textarea_field((string) ($draft['gbp_summary'] ?? '')),
                'cta_type' => sanitize_key((string) ($draft['cta_type'] ?? 'learn_more')),
                'image_url' => esc_url_raw((string) ($draft['image_url'] ?? '')),
                'primary_keyword' => sanitize_text_field((string) ($draft['primary_keyword'] ?? '')),
                'status' => 'scheduled',
                'idempotency_key' => $key,
                'content_hash' => sanitize_text_field((string) ($draft['content_hash'] ?? '')),
                'updated_at' => $now,
            );
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $existing = intval($wpdb->get_var($wpdb->prepare("SELECT id FROM {$table} WHERE idempotency_key = %s", $key)));
            if ($existing > 0) {
                unset($data['status']);
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $wpdb->update($table, $data, array('id' => $existing));
                $updated++;
            } else {
                $data['created_at'] = $now;
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $wpdb->insert($table, $data);
                $inserted++;
            }
        }
        return array(
            'applied' => true,
            'operation' => 'sync_offpage_gbp_social_calendar',
            'summary' => array('inserted' => $inserted, 'updated' => $updated),
            'snapshot' => $this->aysa_agent8_get_gbp_social_calendar_snapshot($project_id),
        );
    }

    private function aysa_agent8_update_gbp_social_post_status($project_id, array $operation, $status) {
        global $wpdb;
        $project_id = intval($project_id);
        $post_id = intval($operation['social_post_id'] ?? 0);
        $status = sanitize_key((string) $status);
        if ($project_id <= 0 || $post_id <= 0 || $status === '') {
            return array('applied' => false, 'reason' => 'social_post_id_required');
        }
        $this->aysa_gbp_social_calendar_ensure_table();
        $table = esc_sql($this->aysa_gbp_social_calendar_table());
        $data = array('status' => $status, 'updated_at' => current_time('mysql'));
        if ($status === 'scheduled') {
            $data['last_error'] = '';
        }
        $formats = array_fill(0, count($data), '%s');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $updated = $wpdb->update($table, $data, array('id' => $post_id, 'project_id' => $project_id), $formats, array('%d', '%d'));
        return array(
            'applied' => $updated !== false,
            'operation' => $status === 'rejected' ? 'reject_offpage_gbp_social_post' : 'retry_offpage_gbp_social_post',
            'summary' => array('social_post_id' => $post_id, 'status' => $status),
            'snapshot' => $this->aysa_agent8_get_gbp_social_calendar_snapshot($project_id),
        );
    }

    public function schedule_gbp_social_calendar_cron() {
        if (function_exists('wp_next_scheduled') && !wp_next_scheduled('aysa_gbp_social_calendar_publish_event')) {
            wp_schedule_event(time() + 300, 'hourly', 'aysa_gbp_social_calendar_publish_event');
        }
    }

    public function handle_gbp_social_calendar_publish_event() {
        if (!$this->aysa_agent8v3_beta_flag('AYSA_GBP_WRITES_ENABLED', false)) {
            return;
        }
        global $wpdb;
        $this->aysa_gbp_social_calendar_ensure_table();
        $table = esc_sql($this->aysa_gbp_social_calendar_table());
        $project_id = $this->aysa_resolve_installed_project_id(0);
        if ($project_id <= 0) {
            return;
        }
        $now = current_time('mysql');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $rows = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT * FROM {$table} WHERE project_id = %d AND status IN ('scheduled','retry') AND scheduled_for <= %s AND review_deadline_at <= %s ORDER BY scheduled_for ASC, id ASC LIMIT 20",
            $project_id,
            $now,
            $now
        ), ARRAY_A);
        $processed_projects = array();
        foreach ((array) $rows as $row) {
            $project_id = intval($row['project_id'] ?? 0);
            if ($project_id <= 0 || isset($processed_projects[$project_id])) {
                continue;
            }
            if ($this->aysa_gbp_social_calendar_project_posted_today($project_id)) {
                $processed_projects[$project_id] = true;
                continue;
            }
            $processed_projects[$project_id] = true;
            $this->aysa_publish_gbp_social_calendar_row($project_id, $row);
        }
    }

    private function aysa_gbp_social_calendar_project_posted_today($project_id) {
        global $wpdb;
        $table = esc_sql($this->aysa_gbp_social_calendar_table());
        $start = gmdate('Y-m-d 00:00:00', current_time('timestamp', true));
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $count = intval($wpdb->get_var($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT COUNT(*) FROM {$table} WHERE project_id = %d AND status = 'published' AND published_at >= %s",
            intval($project_id),
            $start
        )));
        return $count > 0;
    }

    private function aysa_publish_gbp_social_calendar_row($project_id, array $row) {
        global $wpdb;
        $project_id = intval($project_id);
        $row_id = intval($row['id'] ?? 0);
        if ($project_id <= 0 || $row_id <= 0) {
            return;
        }
        $table = esc_sql($this->aysa_gbp_social_calendar_table());
        if (!$this->aysa_gbp_social_source_is_public($row)) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->update($table, array(
                'status' => 'failed',
                'last_error' => 'source_not_public',
                'updated_at' => current_time('mysql'),
            ), array('id' => $row_id, 'project_id' => $project_id));
            return;
        }
        $location_name = $this->aysa_gbp_social_calendar_primary_location($project_id);
        if ($location_name === '') {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->update($table, array(
                'status' => 'failed',
                'last_error' => 'gbp_primary_location_required',
                'updated_at' => current_time('mysql'),
            ), array('id' => $row_id, 'project_id' => $project_id));
            return;
        }
        $summary = sanitize_textarea_field((string) ($row['gbp_summary'] ?? ''));
        if ($summary === '') {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->update($table, array(
                'status' => 'failed',
                'last_error' => 'gbp_summary_required',
                'updated_at' => current_time('mysql'),
            ), array('id' => $row_id, 'project_id' => $project_id));
            return;
        }
        $post = array(
            'summary' => $summary,
            'callToAction' => array(
                'actionType' => 'LEARN_MORE',
                'url' => esc_url_raw((string) ($row['source_url'] ?? '')),
            ),
        );
        $image_url = esc_url_raw((string) ($row['image_url'] ?? ''));
        if ($image_url !== '') {
            $post['media'] = array(array('mediaFormat' => 'PHOTO', 'sourceUrl' => $image_url));
        }
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $wpdb->update($table, array('status' => 'publishing', 'updated_at' => current_time('mysql')), array('id' => $row_id, 'project_id' => $project_id));
        $result = $this->aysa_agent8_apply_offpage_gbp_direct_proxy(
            $project_id,
            '/v1/offpage/gbp/posts/create',
            array('location_name' => $location_name, 'post' => $post),
            'create_offpage_gbp_post',
            array('social_post_id' => $row_id, 'source_url' => esc_url_raw((string) ($row['source_url'] ?? '')))
        );
        if (!empty($result['applied'])) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->update($table, array(
                'status' => 'published',
                'published_at' => current_time('mysql'),
                'gbp_result_json' => wp_json_encode($result),
                'last_error' => '',
                'updated_at' => current_time('mysql'),
            ), array('id' => $row_id, 'project_id' => $project_id));
            return;
        }
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $wpdb->update($table, array(
            'status' => 'failed',
            'gbp_result_json' => wp_json_encode($result),
            'last_error' => sanitize_text_field((string) ($result['reason'] ?? 'gbp_publish_failed')),
            'updated_at' => current_time('mysql'),
        ), array('id' => $row_id, 'project_id' => $project_id));
    }

    private function aysa_gbp_social_source_is_public(array $row) {
        $source_type = sanitize_key((string) ($row['source_type'] ?? ''));
        $source_id = intval($row['source_id'] ?? 0);
        if ($source_id <= 0) {
            return false;
        }
        if ($source_type === 'category') {
            $term = get_term($source_id, 'product_cat');
            return $term && !is_wp_error($term);
        }
        $post = get_post($source_id);
        return $post && (string) $post->post_status === 'publish';
    }

    private function aysa_gbp_social_calendar_primary_location($project_id) {
        $overview = $this->aysa_agent8_get_offpage_gbp_overview_snapshot($project_id);
        $primary = sanitize_text_field((string) ($overview['primary_location_name'] ?? $overview['primary_location'] ?? ''));
        if ($primary !== '') {
            return $primary;
        }
        $locations = isset($overview['locations']) && is_array($overview['locations']) ? $overview['locations'] : array();
        foreach ($locations as $location) {
            if (!is_array($location)) {
                continue;
            }
            $name = sanitize_text_field((string) ($location['name'] ?? $location['location_name'] ?? ''));
            if ($name !== '') {
                return $name;
            }
        }
        return '';
    }

    private function aysa_agent8_get_existing_missing_page_titles($project_id) {
        global $wpdb;
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array();
        }
        $table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if ($exists !== $table) {
            return array();
        }
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $titles = $wpdb->get_col($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT title FROM {$table} WHERE project_id = %d AND strategy_type = 'missing_pages' AND COALESCE(automation_status, '') <> 'deleted'",
            $project_id
        ));
        return is_array($titles) ? array_values(array_map('strval', $titles)) : array();
    }

    private function aysa_agent8_get_technical_legacy_urls() {
        $base = admin_url('admin.php?page=aysa-technical-optimization');
        return array(
            'overview' => $base . '&aysa_tab=site-audit',
            'site_audit' => $base . '&aysa_tab=site-audit',
            'pagespeed' => $base . '&aysa_tab=speed',
            'sitemap_robots' => $base . '&aysa_tab=sitemap',
            'internal_linking' => $base . '&aysa_tab=internal-linking',
            'errors' => $base . '&aysa_tab=errors',
            'redirects' => $base . '&aysa_tab=redirects',
            'gone' => $base . '&aysa_tab=gone',
            'schema' => $base . '&aysa_tab=schema',
        );
    }

    private function aysa_agent8_get_keyword_mapping_review_payload($project_id) {
        $workspace = $this->aysa_keyword_v2_cache_get(intval($project_id), 'keyword_mapping_v2');
        if (!is_array($workspace)) {
            return $this->aysa_agent8_build_keyword_review_payload_from_local_snapshot($project_id);
        }

        $clusters = isset($workspace['clusters']) && is_array($workspace['clusters']) ? $workspace['clusters'] : array();
        $mapped = 0;
        $review = 0;
        $not_mapped = 0;
        foreach ($clusters as $cluster) {
            if (!is_array($cluster)) {
                continue;
            }
            $status = (string) ($cluster['mapping_status'] ?? '');
            if ($status === 'mapped') {
                $mapped++;
            } elseif ($status === 'review') {
                $review++;
            } elseif ($status === 'not_mapped') {
                $not_mapped++;
            }
        }

        return array(
            'summary' => array(
                'clusters_total' => count($clusters),
                'keywords_kept' => count($this->aysa_agent8_build_keyword_review_rows($workspace)),
                'mapped' => $mapped,
                'review' => $review,
                'not_mapped' => $not_mapped,
            ),
            'rows' => $this->aysa_agent8_build_keyword_review_rows($workspace),
        );
    }

    private function aysa_agent8_build_keyword_review_payload_from_local_snapshot($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array(
                'summary' => array(
                    'clusters_total' => 0,
                    'keywords_kept' => 0,
                    'mapped' => 0,
                    'review' => 0,
                    'not_mapped' => 0,
                ),
                'rows' => array(),
            );
        }

        $snapshot = get_option($this->aysa_keyword_discovery_snapshot_key($project_id), array());
        $keywords = isset($snapshot['keywords']) && is_array($snapshot['keywords']) ? $snapshot['keywords'] : array();
        $associations = $this->get_keyword_discovery_local_associations($project_id);
        $association_map = array();
        foreach ($associations as $association) {
            if (!is_array($association)) {
                continue;
            }
            $keyword = strtolower(trim((string) ($association['keyword'] ?? '')));
            if ($keyword === '') {
                continue;
            }
            $association_map[$keyword] = array(
                'associated_page' => trim((string) ($association['associatedOn'] ?? '')),
                'value' => trim((string) ($association['value'] ?? '')),
            );
        }

        $rows = array();
        $mapped = 0;
        $review = 0;
        foreach ($keywords as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keyword = trim((string) ($row['keyword'] ?? $row['query'] ?? ''));
            if ($keyword === '') {
                continue;
            }
            $normalized_keyword = strtolower($keyword);
            $association = isset($association_map[$normalized_keyword]) && is_array($association_map[$normalized_keyword])
                ? $association_map[$normalized_keyword]
                : array();
            $associated_page = trim((string) ($association['associated_page'] ?? ''));
            $status = $associated_page !== '' ? 'mapped' : 'review';
            if ($status === 'mapped') {
                $mapped++;
            } else {
                $review++;
            }
            $rows[] = array(
                'row_key' => md5($keyword . '|' . $associated_page),
                'cluster_id' => md5($keyword),
                'keyword' => $keyword,
                'volume' => max(0, intval($row['search_volume'] ?? $row['volume'] ?? 0)),
                'associated_page' => $associated_page,
                'status' => $status,
                'selected' => true,
            );
        }

        usort($rows, static function ($left, $right) {
            $volume_diff = ((int) ($right['volume'] ?? 0)) <=> ((int) ($left['volume'] ?? 0));
            if ($volume_diff !== 0) {
                return $volume_diff;
            }
            return strcmp((string) ($left['keyword'] ?? ''), (string) ($right['keyword'] ?? ''));
        });

        return array(
            'summary' => array(
                'clusters_total' => count($rows),
                'keywords_kept' => count($rows),
                'mapped' => $mapped,
                'review' => $review,
                'not_mapped' => 0,
            ),
            'rows' => array_values($rows),
            'source' => 'local_keyword_snapshot',
        );
    }

    private function aysa_agent8_build_keyword_review_rows($workspace) {
        $clusters = isset($workspace['clusters']) && is_array($workspace['clusters']) ? $workspace['clusters'] : array();
        $rows = array();
        foreach ($clusters as $cluster) {
            if (!is_array($cluster)) {
                continue;
            }

            $cluster_id = trim((string) ($cluster['cluster_id'] ?? ''));
            if ($cluster_id === '') {
                continue;
            }

            $cluster_match_url = $this->aysa_agent8_extract_keyword_associated_page($cluster);
            $keyword_rows = array();
            $primary = isset($cluster['primary_keyword']) && is_array($cluster['primary_keyword']) ? $cluster['primary_keyword'] : array();
            if (!empty($primary)) {
                $keyword_rows[] = $primary;
            }
            $secondary = isset($cluster['secondary_keywords']) && is_array($cluster['secondary_keywords']) ? $cluster['secondary_keywords'] : array();
            foreach ($secondary as $keyword_row) {
                if (is_array($keyword_row)) {
                    $keyword_rows[] = $keyword_row;
                }
            }

            foreach ($keyword_rows as $keyword_row) {
                $keyword = trim((string) ($keyword_row['keyword'] ?? ''));
                if ($keyword === '') {
                    continue;
                }
                $selection_state = trim((string) ($keyword_row['selection_state'] ?? $cluster['selection_state'] ?? 'review'));
                if ($selection_state === 'excluded') {
                    continue;
                }

                $rows[] = array(
                    'row_key' => $cluster_id . ':' . md5($keyword),
                    'cluster_id' => $cluster_id,
                    'keyword' => $keyword,
                    'volume' => max(0, (int) ($keyword_row['search_volume'] ?? 0)),
                    'associated_page' => $this->aysa_agent8_extract_keyword_associated_page($keyword_row) ?: $cluster_match_url,
                    'status' => $selection_state,
                    'selected' => $selection_state !== 'excluded',
                );
            }
        }

        usort($rows, static function ($left, $right) {
            $left_selected = !empty($left['selected']) ? 1 : 0;
            $right_selected = !empty($right['selected']) ? 1 : 0;
            if ($left_selected !== $right_selected) {
                return $right_selected <=> $left_selected;
            }

            $volume_diff = ((int) ($right['volume'] ?? 0)) <=> ((int) ($left['volume'] ?? 0));
            if ($volume_diff !== 0) {
                return $volume_diff;
            }

            return strcmp((string) ($left['keyword'] ?? ''), (string) ($right['keyword'] ?? ''));
        });

        return array_values($rows);
    }

    private function aysa_agent8_extract_keyword_associated_page($row) {
        if (!is_array($row)) {
            return '';
        }
        $matched_page = isset($row['matched_page']) && is_array($row['matched_page']) ? $row['matched_page'] : array();
        $url = trim((string) ($matched_page['url'] ?? ''));
        if ($url !== '') {
            return $url;
        }

        $best_page = isset($row['best_page_match']) && is_array($row['best_page_match']) ? $row['best_page_match'] : array();
        $url = trim((string) ($best_page['url'] ?? ''));
        if ($url !== '') {
            return $url;
        }

        $matches = isset($row['keyword_page_matches']) && is_array($row['keyword_page_matches']) ? $row['keyword_page_matches'] : array();
        if (!empty($matches)) {
            $first = $matches[0] ?? null;
            if (is_array($first)) {
                $url = trim((string) ($first['url'] ?? ''));
                if ($url !== '') {
                    return $url;
                }
            }
        }

        return '';
    }

    private function aysa_agent8_get_content_strategy_rows($project_id) {
        global $wpdb;
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array();
        }
        $plan_batch = $this->aysa_agent8_current_research_plan_batch($project_id);
        $table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if ($exists !== $table) {
            return array();
        }

        if ($plan_batch !== '') {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $rows = $wpdb->get_results($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT *
" .
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
                "                 FROM {$table}
                 WHERE project_id = %d
                   AND plan_batch = %s
                   AND COALESCE(automation_status, '') <> 'deleted'
                   AND duplicate_of_strategy_id IS NULL
                   AND superseded_by_strategy_id IS NULL
                 ORDER BY id ASC",
                $project_id,
                $plan_batch
            ), ARRAY_A);
            if (is_array($rows) && $rows !== array()) {
                return $rows;
            }
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $rows = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT *
" .
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
            "             FROM {$table}
             WHERE project_id = %d
               AND COALESCE(automation_status, '') <> 'deleted'
               AND duplicate_of_strategy_id IS NULL
               AND superseded_by_strategy_id IS NULL
             ORDER BY id ASC",
            $project_id
        ), ARRAY_A);

        return is_array($rows) ? $rows : array();
    }


    private function aysa_agent8_get_content_strategy_meta_map($project_id) {
        global $wpdb;
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array();
        }
        $table = esc_sql($wpdb->prefix . 'aysa_missing_page_meta');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if ($exists !== $table) {
            return array();
        }
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $rows = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT strategy_id, seo_title, h1, meta_description, warning, updated_at
" .
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
            "             FROM {$table}
             WHERE project_id = %d",
            $project_id
        ), ARRAY_A);
        $map = array();
        foreach ((array) $rows as $row) {
            $strategy_id = intval($row['strategy_id'] ?? 0);
            if ($strategy_id > 0) {
                $map[$strategy_id] = $row;
            }
        }
        return $map;
    }

    private function aysa_agent8_get_content_strategy_status_map($project_id) {
        global $wpdb;
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array();
        }
        $plan_batch = $this->aysa_agent8_current_research_plan_batch($project_id);
        $table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if ($exists !== $table) {
            return array();
        }
        if ($plan_batch !== '') {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $rows = $wpdb->get_results($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT COALESCE(automation_status, 'none') AS s, COUNT(*) AS c
" .
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
                "                 FROM {$table}
                 WHERE project_id = %d
                   AND plan_batch = %s
                 GROUP BY COALESCE(automation_status, 'none')",
                $project_id,
                $plan_batch
            ), ARRAY_A);
            $map = array();
            foreach ((array) $rows as $row) {
                $map[(string) ($row['s'] ?? 'none')] = intval($row['c'] ?? 0);
            }
            if ($map !== array()) {
                return $map;
            }
        }
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $rows = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT COALESCE(automation_status, 'none') AS s, COUNT(*) AS c
" .
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
            "             FROM {$table}
             WHERE project_id = %d
             GROUP BY COALESCE(automation_status, 'none')",
            $project_id
        ), ARRAY_A);
        $map = array();
        foreach ((array) $rows as $row) {
            $map[(string) ($row['s'] ?? 'none')] = intval($row['c'] ?? 0);
        }
        return $map;
    }

    private function aysa_agent8_get_content_strategy_next_item($project_id) {
        global $wpdb;
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return null;
        }
        $plan_batch = $this->aysa_agent8_current_research_plan_batch($project_id);
        $table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if ($exists !== $table) {
            return null;
        }
        if ($plan_batch !== '') {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $row = $wpdb->get_row($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT id, title, scheduled_for
" .
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
                "                 FROM {$table}
                 WHERE project_id = %d
                   AND plan_batch = %s
                   AND status = 'scheduled'
                   AND automation_status IN ('queued', 'retry')
                   AND scheduled_for IS NOT NULL
                 ORDER BY scheduled_for ASC
                 LIMIT 1",
                $project_id,
                $plan_batch
            ), ARRAY_A);
            if (is_array($row)) {
                return $row;
            }
        }
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $row = $wpdb->get_row($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT id, title, scheduled_for
" .
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
            "             FROM {$table}
             WHERE project_id = %d
               AND status = 'scheduled'
               AND automation_status IN ('queued', 'retry')
               AND scheduled_for IS NOT NULL
             ORDER BY scheduled_for ASC
             LIMIT 1",
            $project_id
        ), ARRAY_A);
        return is_array($row) ? $row : null;
    }

    private function aysa_agent8_get_publishing_calendar_rows($project_id) {
        global $wpdb;
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array();
        }
        $plan_batch = $this->aysa_agent8_current_research_plan_batch($project_id);
        $table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if ($exists !== $table) {
            return array();
        }
        if ($plan_batch !== '') {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $rows = $wpdb->get_results($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT id, title, strategy_type, recommended_page_type, content_type, execution_stage, intent, status, post_id, scheduled_for, generated_at, automation_status
" .
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
                "                 FROM {$table}
                 WHERE project_id = %d
                   AND plan_batch = %s
                   AND COALESCE(automation_status, '') <> 'deleted'
                   AND duplicate_of_strategy_id IS NULL
                   AND superseded_by_strategy_id IS NULL
                 ORDER BY COALESCE(scheduled_for, created_at) ASC",
                $project_id,
                $plan_batch
            ), ARRAY_A);
            if (is_array($rows) && $rows !== array()) {
                return $rows;
            }
        }
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $rows = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT id, title, strategy_type, recommended_page_type, content_type, execution_stage, intent, status, post_id, scheduled_for, generated_at, automation_status
" .
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
            "             FROM {$table}
             WHERE project_id = %d
               AND COALESCE(automation_status, '') <> 'deleted'
               AND duplicate_of_strategy_id IS NULL
               AND superseded_by_strategy_id IS NULL
             ORDER BY COALESCE(scheduled_for, created_at) ASC",
            $project_id
        ), ARRAY_A);
        return is_array($rows) ? $rows : array();
    }

    private function aysa_agent8_strategy_type_label($strategy_type) {
        $strategy_type = sanitize_key((string) $strategy_type);
        switch ($strategy_type) {
            case 'cluster_titles':
            case 'cluster_based':
                return 'Cluster-based';
            case 'competitor_derived_titles':
            case 'competitor_analysis':
                return 'Competitor Analysis';
            case 'competitor_sitemap':
                return 'Legacy Content From Sitemaps';
            case 'intent_layering':
                return 'Intent Layering';
            case 'seasonal_event':
            case 'seasonal_events':
                return 'Seasonal & Events';
            case 'serp_features':
                return 'SERP Features';
            case 'evergreen_fresh_mix':
            case 'evergreen_fresh':
                return 'Evergreen + Fresh';
            case 'customer_journey':
                return 'Customer Journey';
            case 'local_seo':
                return 'Local SEO';
            case 'authority_building':
                return 'Authority Building';
            case 'persona_based':
                return 'Persona-based';
            case 'missing_pages':
                return 'Missing Pages Promoted To Plan';
            default:
                return ucwords(str_replace(array('_', '-'), ' ', $strategy_type));
        }
    }

    private function aysa_agent8_build_content_strategy_review_command($rows) {
        $rows = is_array($rows) ? $rows : array();
        $groups = array();
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $group_key = sanitize_key((string) ($row['strategy_type'] ?? 'content_strategy'));
            if ($group_key === '') {
                $group_key = 'content_strategy';
            }
            if (!isset($groups[$group_key])) {
                $groups[$group_key] = array(
                    'group_key' => $group_key,
                    'label' => $this->aysa_agent8_strategy_type_label($group_key),
                    'count' => 0,
                    'items' => array(),
                );
            }

            $keywords = array();
            if (isset($row['keywords'])) {
                $decoded = is_string($row['keywords']) ? json_decode($row['keywords'], true) : $row['keywords'];
                if (is_array($decoded)) {
                    $keywords = array_values(array_filter(array_map('strval', $decoded)));
                }
            }

            $groups[$group_key]['items'][] = array(
                'id' => intval($row['id'] ?? 0),
                'title' => trim((string) ($row['title'] ?? '')),
                'keywords' => $keywords,
                'recommended_page_type' => trim((string) ($row['recommended_page_type'] ?? $row['content_type'] ?? 'article')),
                'status' => trim((string) ($row['status'] ?? 'proposed')),
                'automation_status' => trim((string) ($row['automation_status'] ?? '')),
                'execution_stage' => trim((string) ($row['execution_stage'] ?? 'planned')),
                'associated_page' => trim((string) ($row['associated_page'] ?? '')),
                'scheduled_for' => trim((string) ($row['scheduled_for'] ?? '')),
                'generated_at' => trim((string) ($row['generated_at'] ?? '')),
                'published_at' => trim((string) ($row['published_at'] ?? '')),
                'edit_url' => trim((string) ($row['edit_url'] ?? '')),
                'public_url' => trim((string) ($row['public_url'] ?? '')),
            );
            $groups[$group_key]['count']++;
        }

        return array(
            'type' => 'open_content_strategy_review',
            'title' => 'Content Strategy',
            'intro' => 'Aici poți revizui fiecare articol propus, poți refuza itemii care nu sunt potriviți și poți asocia o pagină sau o adresă relevantă înainte să continui.',
            'groups' => array_values($groups),
        );
    }

    private function aysa_agent8_apply_content_strategy_updates($project_id, $updates) {
        global $wpdb;
        $project_id = intval($project_id);
        $updates = is_array($updates) ? $updates : array();
        if ($project_id <= 0 || $updates === array()) {
            return array('applied' => false, 'reason' => 'empty_content_strategy_updates');
        }

        $table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if ($exists !== $table) {
            return array('applied' => false, 'reason' => 'content_strategy_table_missing');
        }

        $updated = 0;
        $rejected = 0;
        $associated = 0;
        foreach ($updates as $item) {
            if (!is_array($item)) {
                continue;
            }
            $strategy_id = intval($item['id'] ?? 0);
            if ($strategy_id <= 0) {
                continue;
            }

            $data = array('automation_updated_at' => current_time('mysql'));
            $format = array('%s');
            if (!empty($item['rejected'])) {
                $data['automation_status'] = 'deleted';
                $format[] = '%s';
                $rejected++;
            } else {
                $data['associated_page'] = sanitize_text_field((string) ($item['associated_page'] ?? ''));
                $format[] = '%s';
                if ($data['associated_page'] !== '') {
                    $associated++;
                }
            }

            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $result = $wpdb->update(
                $table,
                $data,
                array(
                    'project_id' => $project_id,
                    'id' => $strategy_id,
                ),
                $format,
                array('%d', '%d')
            );
            if ($result !== false) {
                $updated++;
            }
        }

        $this->aysa_keyword_v2_cache_delete($project_id, 'content_strategy_v2');

        return array(
            'applied' => true,
            'updated' => $updated,
            'rejected' => $rejected,
            'associated' => $associated,
        );
    }

    private function aysa_agent8_apply_local_mutations($project_id, $mutations) {
        $project_id = intval($project_id);
        $mutations = is_array($mutations) ? $mutations : array();
        if ($project_id <= 0 || empty($mutations)) {
            return array('applied' => false, 'reason' => 'empty_mutations');
        }

        $mutation_results = array();
        $handled_mutations = array();
        $collect_mutation_result = static function ($key, $result) use (&$mutation_results, &$handled_mutations) {
            $key = sanitize_key((string) $key);
            if ($key === '') {
                return;
            }
            $mutation_results[$key] = is_array($result)
                ? $result
                : array(
                    'applied' => false,
                    'reason' => 'invalid_mutation_result',
                );
            $handled_mutations[] = $key;
        };

        $tone_mutation = isset($mutations['tone_of_voice']) && is_array($mutations['tone_of_voice'])
            ? $mutations['tone_of_voice']
            : array();
        if ($tone_mutation !== array()) {
            $selected_tone = sanitize_text_field((string) ($tone_mutation['selected_tone'] ?? ''));
            if ($selected_tone === '') {
                $collect_mutation_result('tone_of_voice', array('applied' => false, 'reason' => 'invalid_tone_mutation'));
            } else {
                if (!class_exists('Aysa_Auth_Manager')) {
                    require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
                }

                try {
                    $auth_manager = Aysa_Auth_Manager::get_instance();
                    $existing = $auth_manager->make_authenticated_request('api/ToneOfVoice/ByProject/' . $project_id, 'GET');

                    $existing_id = 0;
                    if (is_array($existing)) {
                        if (!empty($existing['id'])) {
                            $existing_id = intval($existing['id']);
                        } elseif (isset($existing['data']) && is_array($existing['data']) && !empty($existing['data']['id'])) {
                            $existing_id = intval($existing['data']['id']);
                        }
                    }

                    $operation = 'create';
                    $ok = false;
                    if ($existing_id > 0) {
                        $operation = 'update';
                        $ok = $auth_manager->make_authenticated_request(
                            'api/ToneOfVoice/UpdateTone/' . $existing_id,
                            'PATCH',
                            array('selectedTone' => $selected_tone)
                        );
                    }

                    if (!$ok) {
                        $operation = 'create';
                        $ok = $auth_manager->make_authenticated_request(
                            'api/ToneOfVoice',
                            'POST',
                            array(
                                'projectId' => $project_id,
                                'selectedTone' => $selected_tone,
                            )
                        );
                    }

                    if (!$ok) {
                        $collect_mutation_result('tone_of_voice', array('applied' => false, 'reason' => 'tone_api_save_failed'));
                    } else {
                        $collect_mutation_result('tone_of_voice', array(
                            'applied' => true,
                            'tone_saved' => true,
                            'operation' => $operation,
                            'selected_tone' => $selected_tone,
                        ));
                    }
                } catch (Exception $e) {
                    $collect_mutation_result('tone_of_voice', array(
                        'applied' => false,
                        'reason' => 'tone_api_exception',
                        'message' => $e->getMessage(),
                    ));
                }
            }
        }

        $content_strategy_updates = isset($mutations['content_strategy_updates']) && is_array($mutations['content_strategy_updates'])
            ? $mutations['content_strategy_updates']
            : array();
        if ($content_strategy_updates !== array()) {
            $collect_mutation_result(
                'content_strategy_updates',
                $this->aysa_agent8_apply_content_strategy_updates($project_id, $content_strategy_updates)
            );
        }

        $onpage_operation = isset($mutations['onpage_operation']) && is_array($mutations['onpage_operation'])
            ? $mutations['onpage_operation']
            : array();
        if ($onpage_operation !== array()) {
            $operation = sanitize_key((string) ($onpage_operation['operation'] ?? ''));
            $collect_mutation_result('onpage_operation', match ($operation) {
                'accept_onpage_keyword_association' => $this->aysa_agent8_apply_onpage_mapping_operation($project_id, $onpage_operation, 'accept_mapping'),
                'reject_onpage_keyword_association' => $this->aysa_agent8_apply_onpage_mapping_operation($project_id, $onpage_operation, 'reject_mapping'),
                'apply_onpage_meta_for_page' => $this->aysa_agent8_apply_onpage_entity_operation($project_id, $onpage_operation, 'apply_meta'),
                'apply_onpage_rewrite_for_page' => $this->aysa_agent8_apply_onpage_entity_operation($project_id, $onpage_operation, 'apply_rewrite'),
                'skip_onpage_page' => $this->aysa_agent8_apply_onpage_entity_operation($project_id, $onpage_operation, 'skip'),
                'refresh_onpage_page' => $this->aysa_agent8_apply_onpage_entity_operation($project_id, $onpage_operation, 'refresh'),
                'apply_onpage_meta_batch', 'apply_meta' => $this->aysa_agent8_apply_onpage_batch_operation($project_id, $onpage_operation, 'apply_meta'),
                'apply_onpage_rewrite_batch', 'apply_rewrite' => $this->aysa_agent8_apply_onpage_batch_operation($project_id, $onpage_operation, 'apply_rewrite'),
                default => array('applied' => false, 'reason' => 'unsupported_onpage_operation'),
            });
        }

        $offpage_operation = isset($mutations['offpage_operation']) && is_array($mutations['offpage_operation'])
            ? $mutations['offpage_operation']
            : array();
        if ($offpage_operation !== array()) {
            $operation = sanitize_key((string) ($offpage_operation['operation'] ?? ''));
            $collect_mutation_result('offpage_operation', match ($operation) {
                'set_offpage_gbp_primary_location' => $this->aysa_agent8_apply_offpage_gbp_primary_location($project_id, $offpage_operation),
                'refresh_offpage_gbp_reviews' => $this->aysa_agent8_apply_offpage_gbp_reviews_refresh($project_id, $offpage_operation),
                'refresh_offpage_gbp_performance' => $this->aysa_agent8_apply_offpage_gbp_performance_refresh($project_id, $offpage_operation),
                'create_offpage_gbp_post' => $this->aysa_agent8_apply_offpage_gbp_create_post($project_id, $offpage_operation),
                'reply_offpage_gbp_review' => $this->aysa_agent8_apply_offpage_gbp_reply_review($project_id, $offpage_operation),
                'upload_offpage_gbp_media' => $this->aysa_agent8_apply_offpage_gbp_upload_media($project_id, $offpage_operation),
                'update_offpage_gbp_business_info' => $this->aysa_agent8_apply_offpage_gbp_update_business_info($project_id, $offpage_operation),
                'update_offpage_gbp_hours' => $this->aysa_agent8_apply_offpage_gbp_update_hours($project_id, $offpage_operation),
                'update_offpage_gbp_attributes' => $this->aysa_agent8_apply_offpage_gbp_update_attributes($project_id, $offpage_operation),
                'update_offpage_gbp_services' => $this->aysa_agent8_apply_offpage_gbp_update_services($project_id, $offpage_operation),
                'sync_offpage_gbp_social_calendar' => $this->aysa_agent8_sync_gbp_social_calendar($project_id, $offpage_operation),
                'reject_offpage_gbp_social_post' => $this->aysa_agent8_update_gbp_social_post_status($project_id, $offpage_operation, 'rejected'),
                'retry_offpage_gbp_social_post' => $this->aysa_agent8_update_gbp_social_post_status($project_id, $offpage_operation, 'scheduled'),
                'run_offpage_backlink_audit' => $this->aysa_agent8_run_offpage_backlink_audit($project_id, $offpage_operation),
                'sync_offpage_backlink_audit_snapshot' => $this->aysa_agent8_sync_offpage_backlink_audit_snapshot($project_id, $offpage_operation),
                'sync_offpage_backlink_strategy_snapshot' => $this->aysa_agent8_sync_offpage_backlink_strategy_snapshot($project_id, $offpage_operation),
                'clear_offpage_backlink_strategy_snapshot' => $this->aysa_agent8_clear_offpage_backlink_strategy_snapshot($project_id, $offpage_operation),
                'sync_offpage_adverlink_article' => $this->aysa_agent8_sync_offpage_adverlink_article($project_id, $offpage_operation),
                'publish_offpage_adverlink_article' => $this->aysa_agent8_publish_offpage_adverlink_article($project_id, $offpage_operation),
                'queue_offpage_publisher_article_order' => $this->aysa_agent8_queue_offpage_publisher_article_order($project_id, $offpage_operation),
                'preview_offpage_link_building_cart' => $this->aysa_agent8_preview_offpage_link_building_cart($project_id, $offpage_operation),
                'send_offpage_draft_article_approval_email' => $this->aysa_agent8_send_offpage_draft_article_approval_email($project_id, $offpage_operation),
                'recommend_offpage_link_insertion' => $this->aysa_agent8_recommend_offpage_purchase($project_id, $offpage_operation, 'link_insertion'),
                'preview_offpage_link_insertion_order' => $this->aysa_agent8_preview_offpage_purchase($project_id, $offpage_operation, 'link_insertion'),
                'send_offpage_link_insertion_approval_email' => $this->aysa_agent8_send_offpage_purchase_approval_email($project_id, $offpage_operation, 'link_insertion'),
                'place_offpage_link_insertion_order' => $this->aysa_agent8_place_offpage_purchase_order($project_id, $offpage_operation, 'link_insertion'),
                'recommend_offpage_seo_text_ads' => $this->aysa_agent8_recommend_offpage_purchase($project_id, $offpage_operation, 'seo_text_ads'),
                'preview_offpage_seo_text_ads_order' => $this->aysa_agent8_preview_offpage_purchase($project_id, $offpage_operation, 'seo_text_ads'),
                'send_offpage_seo_text_ads_approval_email' => $this->aysa_agent8_send_offpage_purchase_approval_email($project_id, $offpage_operation, 'seo_text_ads'),
                'place_offpage_seo_text_ads_order' => $this->aysa_agent8_place_offpage_purchase_order($project_id, $offpage_operation, 'seo_text_ads'),
                'approve_offpage_draft_article' => $this->aysa_agent8_approve_offpage_draft_article($project_id, array(
                    'draft_id' => intval($offpage_operation['draft_id'] ?? 0),
                )),
                'find_offpage_link_exchange_matches' => $this->aysa_agent8_refresh_link_exchange_matches_snapshot($project_id),
                'request_offpage_link_exchange' => $this->aysa_agent8_create_link_exchange_request_from_payload($project_id, $offpage_operation),
                'set_offpage_link_exchange_autoactivation' => $this->aysa_agent8_set_link_exchange_autoactivation($project_id, !empty($offpage_operation['enabled'])),
                'accept_offpage_exchange_request' => $this->aysa_agent8_start_offpage_async_operation($project_id, 'exchange_request_respond', array(
                    'request_id' => intval($offpage_operation['request_id'] ?? 0),
                    'exchange_ref' => sanitize_text_field((string) ($offpage_operation['exchange_ref'] ?? '')),
                    'decision' => sanitize_text_field((string) ($offpage_operation['decision'] ?? 'accept')),
                    'counterparty_target_url' => esc_url_raw((string) ($offpage_operation['counterparty_target_url'] ?? '')),
                    'counterparty_anchor_text' => sanitize_text_field((string) ($offpage_operation['counterparty_anchor_text'] ?? '')),
                    'counterparty_notes' => sanitize_textarea_field((string) ($offpage_operation['counterparty_notes'] ?? '')),
                )),
                'simulate_accept_offpage_exchange_request' => $this->aysa_agent8_run_offpage_operation_now($project_id, 'exchange_simulate_accept', array(
                    'request_id' => intval($offpage_operation['request_id'] ?? 0),
                    'exchange_ref' => sanitize_text_field((string) ($offpage_operation['exchange_ref'] ?? '')),
                    'source' => sanitize_text_field((string) ($offpage_operation['source'] ?? 'agent8_v3_legacy_admin')),
                )),
                'insert_offpage_exchange_partner_box' => $this->aysa_agent8_run_offpage_operation_now($project_id, 'exchange_activate_link', array(
                    'request_id' => intval($offpage_operation['request_id'] ?? 0),
                    'exchange_ref' => sanitize_text_field((string) ($offpage_operation['exchange_ref'] ?? '')),
                    'source_post_id' => intval($offpage_operation['source_post_id'] ?? 0),
                    'notes' => sanitize_textarea_field((string) ($offpage_operation['notes'] ?? 'Agent8 V3 partner box fallback.')),
                )),
                'verify_offpage_exchange_request' => $this->aysa_agent8_run_offpage_operation_now($project_id, 'exchange_refresh_verification', array(
                    'request_id' => intval($offpage_operation['request_id'] ?? 0),
                    'exchange_ref' => sanitize_text_field((string) ($offpage_operation['exchange_ref'] ?? '')),
                )),
                'pause_offpage_exchange_request' => $this->aysa_agent8_start_offpage_async_operation($project_id, 'exchange_change_state', array(
                    'request_id' => intval($offpage_operation['request_id'] ?? 0),
                    'exchange_ref' => sanitize_text_field((string) ($offpage_operation['exchange_ref'] ?? '')),
                    'state_action' => sanitize_text_field((string) ($offpage_operation['state_action'] ?? 'pause')),
                    'reason' => sanitize_textarea_field((string) ($offpage_operation['reason'] ?? 'Paused from Agent8')),
                )),
                default => array('applied' => false, 'reason' => 'unsupported_offpage_operation'),
            });
        }

        $technical_site_audit_state = isset($mutations['technical_site_audit_state']) && is_array($mutations['technical_site_audit_state'])
            ? $mutations['technical_site_audit_state']
            : array();
        if ($technical_site_audit_state !== array()) {
            $collect_mutation_result(
                'technical_site_audit_state',
                $this->aysa_agent8_sync_technical_site_audit_state($project_id, $technical_site_audit_state)
            );
        }

        $technical_pagespeed_state = isset($mutations['technical_pagespeed_state']) && is_array($mutations['technical_pagespeed_state'])
            ? $mutations['technical_pagespeed_state']
            : array();
        if ($technical_pagespeed_state !== array()) {
            $collect_mutation_result(
                'technical_pagespeed_state',
                $this->aysa_agent8_sync_technical_pagespeed_state($project_id, $technical_pagespeed_state)
            );
        }

        $technical_schema_state = isset($mutations['technical_schema_state']) && is_array($mutations['technical_schema_state'])
            ? $mutations['technical_schema_state']
            : array();
        if ($technical_schema_state !== array()) {
            $collect_mutation_result(
                'technical_schema_state',
                $this->aysa_agent8_sync_technical_schema_state($project_id, $technical_schema_state)
            );
        }

        $technical_redirects_state = isset($mutations['technical_redirects_state']) && is_array($mutations['technical_redirects_state'])
            ? $mutations['technical_redirects_state']
            : array();
        if ($technical_redirects_state !== array()) {
            $collect_mutation_result(
                'technical_redirects_state',
                $this->aysa_agent8_sync_technical_redirects_state($project_id, $technical_redirects_state)
            );
        }

        $technical_sitemap_robots_state = isset($mutations['technical_sitemap_robots_state']) && is_array($mutations['technical_sitemap_robots_state'])
            ? $mutations['technical_sitemap_robots_state']
            : array();
        if ($technical_sitemap_robots_state !== array()) {
            $collect_mutation_result(
                'technical_sitemap_robots_state',
                $this->aysa_agent8_sync_technical_sitemap_robots_state($project_id, $technical_sitemap_robots_state)
            );
        }

        $technical_operation = isset($mutations['technical_operation']) && is_array($mutations['technical_operation'])
            ? $mutations['technical_operation']
            : array();
        if ($technical_operation !== array()) {
            $operation = sanitize_key((string) ($technical_operation['operation'] ?? ''));
            $collect_mutation_result('technical_operation', match ($operation) {
                'refresh_technical_site_audit' => array(
                    'applied' => true,
                    'operation' => $operation,
                    'snapshot' => $this->aysa_adjustments_run_technical_site_audit_refresh($project_id, true),
                ),
                'refresh_technical_pagespeed' => array(
                    'applied' => true,
                    'operation' => $operation,
                    'snapshot' => $this->aysa_adjustments_run_technical_pagespeed_refresh($project_id, true),
                ),
                'email_technical_pagespeed_pdf' => $this->aysa_agent8_email_technical_pagespeed_pdf($project_id, $technical_operation),
                'refresh_technical_sitemap_robots' => array(
                    'applied' => true,
                    'operation' => $operation,
                    'snapshot' => $this->aysa_adjustments_run_technical_sitemap_robots_refresh($project_id, true),
                ),
                'unblock_technical_robots' => $this->aysa_agent8_apply_unblock_technical_robots($project_id),
                'generate_aysa_sitemap' => $this->aysa_agent8_generate_technical_sitemap($project_id),
                'save_technical_robots' => $this->aysa_agent8_save_technical_robots($project_id, $technical_operation),
                'restore_recommended_robots' => $this->aysa_agent8_restore_technical_robots($project_id),
                'enforce_aysa_sitemap_authority' => $this->aysa_agent8_enforce_aysa_sitemap_authority($project_id),
                'disable_conflicting_sitemap_providers' => $this->aysa_agent8_enforce_aysa_sitemap_authority($project_id),
                'preview_technical_internal_linking' => array(
                    'applied' => true,
                    'operation' => $operation,
                    'preview' => $this->aysa_adjustments_run_technical_internal_linking_preview($project_id),
                    'preview_package' => $this->aysa_internal_linking_workspace_service()
                        ? $this->aysa_internal_linking_workspace_service()->build_preview_package($project_id)
                        : array(),
                    'snapshot' => $this->aysa_adjustments_get_technical_internal_linking_snapshot($project_id),
                ),
                'apply_technical_internal_linking' => array(
                    'applied' => false,
                    'operation' => $operation,
                    'guarded' => true,
                    'reason' => 'approved_preview_package_required',
                    'message' => 'Apply disabled. Requires approved package.',
                    'snapshot' => $this->aysa_adjustments_get_technical_internal_linking_snapshot($project_id),
                ),
                'apply_technical_schema_recommended' => $this->aysa_agent8_apply_technical_schema_operation($project_id, $technical_operation),
                'disable_technical_schema' => $this->aysa_agent8_disable_technical_schema_operation($project_id, $technical_operation),
                'apply_technical_redirect_candidate' => $this->aysa_agent8_apply_technical_redirect_candidate($project_id, $technical_operation),
                'mark_technical_gone_candidate' => $this->aysa_agent8_mark_technical_gone_candidate($project_id, $technical_operation),
                'ignore_technical_404_candidate' => $this->aysa_agent8_ignore_technical_404_candidate($project_id, $technical_operation),
                'apply_technical_redirects_batch' => $this->aysa_agent8_apply_technical_redirect_batch($project_id, $technical_operation, 'redirect'),
                'mark_technical_gone_batch' => $this->aysa_agent8_apply_technical_redirect_batch($project_id, $technical_operation, 'gone'),
                'ignore_technical_404_batch' => $this->aysa_agent8_apply_technical_redirect_batch($project_id, $technical_operation, 'ignore'),
                default => array('applied' => false, 'reason' => 'unsupported_technical_operation'),
            });
        }

        $monitoring_operation = isset($mutations['monitoring_operation']) && is_array($mutations['monitoring_operation'])
            ? $mutations['monitoring_operation']
            : array();
        if ($monitoring_operation !== array()) {
            $operation = sanitize_key((string) ($monitoring_operation['operation'] ?? ''));
            if (in_array($operation, array('refresh_monitoring_rank_tracking', 'refresh_monitoring_ai_monitoring', 'refresh_monitoring_keyword_overview'), true)) {
                $collect_mutation_result('monitoring_operation', array(
                    'applied' => false,
                    'operation' => $operation,
                    'reason' => 'legacy_monitoring_refresh_quarantined_for_agent8_v3',
                    'message' => 'Agent8 V3 nu poate porni refresh-ul Monitoring prin ruta legacy locală.',
                ));
            } else {
                $collect_mutation_result('monitoring_operation', match ($operation) {
                    'save_monitoring_query_pack' => $this->aysa_agent8_apply_monitoring_query_pack_save($project_id, $monitoring_operation),
                    default => array('applied' => false, 'reason' => 'unsupported_monitoring_operation'),
                });
            }
        }

        $research_operation = isset($mutations['research_operation']) && is_array($mutations['research_operation'])
            ? $mutations['research_operation']
            : array();
        if ($research_operation !== array()) {
            $operation = sanitize_key((string) ($research_operation['operation'] ?? ''));
            $collect_mutation_result('research_operation', match ($operation) {
                'refresh_research_keyword_discovery' => $this->aysa_agent8_apply_research_keyword_discovery_refresh($project_id),
                'refresh_research_competitor_gap' => $this->aysa_agent8_apply_research_competitor_gap_refresh($project_id),
                'refresh_research_competitor_identification' => $this->aysa_agent8_apply_research_competitor_identification_refresh($project_id),
                'refresh_research_competitor_backlinks' => $this->aysa_agent8_apply_research_competitor_backlinks_refresh($project_id),
                'refresh_research_sitemap_competition' => $this->aysa_agent8_apply_research_sitemap_competition_refresh($project_id),
                default => array('applied' => false, 'reason' => 'unsupported_research_operation'),
            });
        }

        $insert_rows = isset($mutations['content_strategy_rows_to_insert']) && is_array($mutations['content_strategy_rows_to_insert'])
            ? $mutations['content_strategy_rows_to_insert']
            : array();
        if ($insert_rows !== array()) {
            $result = $this->aysa_missing_pages_insert_plan_rows($project_id, $insert_rows);
            $this->aysa_keyword_v2_cache_delete($project_id, 'content_strategy_v2');
            $this->aysa_keyword_v2_cache_delete($project_id, 'missing_pages_v2');

            $collect_mutation_result('content_strategy_rows_to_insert', array(
                'applied' => true,
                'inserted' => intval($result['added'] ?? 0),
                'existing' => intval($result['existing'] ?? 0),
                'errors' => intval($result['errors'] ?? 0),
            ));
        }

        $schedule_plan = isset($mutations['content_strategy_schedule_plan']) && is_array($mutations['content_strategy_schedule_plan'])
            ? $mutations['content_strategy_schedule_plan']
            : array();
        if ($schedule_plan !== array()) {
            $collect_mutation_result(
                'content_strategy_schedule_plan',
                $this->aysa_agent8_schedule_content_strategy_plan($project_id, $schedule_plan)
            );
        }

        if ($mutation_results === array()) {
            return array('applied' => false, 'reason' => 'no_supported_mutations');
        }

        if (count($mutation_results) === 1) {
            return reset($mutation_results);
        }

        $applied_any = false;
        $summary = array(
            'processed_mutations' => count($mutation_results),
            'applied_mutations' => 0,
            'failed_mutations' => 0,
        );
        $operations = array();
        $snapshot = array();
        $async_job = array();
        foreach ($mutation_results as $result) {
            if (!empty($result['applied'])) {
                $applied_any = true;
                $summary['applied_mutations']++;
            } else {
                $summary['failed_mutations']++;
            }

            if (!empty($result['operation'])) {
                $operations[] = sanitize_key((string) $result['operation']);
            }
            if (isset($result['summary']) && is_array($result['summary'])) {
                $summary = array_merge($summary, $result['summary']);
            }
            if (isset($result['snapshot']) && is_array($result['snapshot']) && $result['snapshot'] !== array()) {
                $snapshot = $result['snapshot'];
            }
            if (isset($result['async_job']) && is_array($result['async_job']) && $result['async_job'] !== array()) {
                $async_job = $result['async_job'];
            }
        }

        aysa_debug_log('[AYSA AGENT8 MUTATIONS] Processed multiple local mutation blocks: ' . wp_json_encode(array(
            'project_id' => $project_id,
            'handled_mutations' => array_values($handled_mutations),
            'operations' => array_values(array_unique(array_filter($operations))),
            'applied_any' => $applied_any,
        )));

        $aggregate_result = array(
            'applied' => $applied_any,
            'handled_mutations' => array_values($handled_mutations),
            'results' => $mutation_results,
            'summary' => $summary,
        );
        if (!$applied_any) {
            $aggregate_result['reason'] = 'no_mutations_applied';
        }
        if ($snapshot !== array()) {
            $aggregate_result['snapshot'] = $snapshot;
        }
        if ($async_job !== array()) {
            $aggregate_result['async_job'] = $async_job;
        }

        return $aggregate_result;
    }

    private function aysa_agent8_schedule_content_strategy_plan($project_id, $mutation) {
        global $wpdb;
        $project_id = intval($project_id);
        $mutation = is_array($mutation) ? $mutation : array();
        if ($project_id <= 0) {
            return array('applied' => false, 'reason' => 'invalid_project_id');
        }

        $table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if ($exists !== $table) {
            return array('applied' => false, 'reason' => 'content_strategy_table_missing');
        }

        $limit = max(0, intval($mutation['limit'] ?? 0));
        $plan_batch = $this->aysa_agent8_current_research_plan_batch($project_id);
        $where_batch = '';
        $params = array($project_id);
        if ($plan_batch !== '') {
            $where_batch = ' AND plan_batch = %s';
            $params[] = $plan_batch;
        }

        $sql = "SELECT id FROM {$table}
                WHERE project_id = %d
                  {$where_batch}
                  AND post_id IS NULL
                  AND scheduled_for IS NOT NULL
                  AND COALESCE(automation_status, '') NOT IN ('deleted','generated','queued','retry','processing','failed')
                  AND status IN ('proposed','planned','')
                  AND duplicate_of_strategy_id IS NULL
                  AND superseded_by_strategy_id IS NULL
                ORDER BY scheduled_for ASC, id ASC";
        if ($limit > 0) {
            $sql .= ' LIMIT ' . intval($limit);
        }

        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query: table identifiers and structure are internal, and request-derived values are prepared before execution. Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $ids = $wpdb->get_col($wpdb->prepare($sql, $params));
        $ids = array_values(array_filter(array_map('intval', is_array($ids) ? $ids : array())));
        if ($ids === array()) {
            return array(
                'applied' => true,
                'scheduled' => 0,
                'queued' => 0,
                'reason' => 'no_dated_proposed_items',
            );
        }

        $placeholders = implode(',', array_fill(0, count($ids), '%d'));
        $update_sql = "UPDATE {$table}
                       SET status = 'scheduled',
                           automation_status = 'queued',
                           automation_attempts = 0,
                           last_error = NULL,
                           automation_updated_at = %s
                       WHERE project_id = %d
                         AND id IN ({$placeholders})";
        $update_params = array_merge(array(current_time('mysql'), $project_id), $ids);
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query: table identifiers and structure are internal, and request-derived values are prepared before execution. Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $updated = $wpdb->query($wpdb->prepare($update_sql, $update_params));

        wp_schedule_single_event(time() + 15, 'aysa_process_planned_content_now');
        $this->aysa_keyword_v2_cache_delete($project_id, 'content_strategy_v2');

        return array(
            'applied' => $updated !== false,
            'scheduled' => intval($updated),
            'queued' => intval($updated),
            'ids' => array_slice($ids, 0, 20),
            'plan_batch' => $plan_batch,
        );
    }

    private function aysa_agent8_current_research_plan_batch($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return '';
        }

        $run_id = intval(get_option($this->aysa_keyword_research_projection_applied_run_option_key($project_id), 0));
        if ($run_id <= 0) {
            return '';
        }

        return 'keyword_research_run_' . $run_id;
    }

    private function aysa_agent8_sync_technical_site_audit_state($project_id, $mutation) {
        $project_id = intval($project_id);
        $mutation = is_array($mutation) ? $mutation : array();
        if ($project_id <= 0) {
            return array('applied' => false, 'reason' => 'invalid_project_id');
        }

        $job_id = sanitize_text_field((string) ($mutation['job_id'] ?? ''));
        $run_id = intval($mutation['run_id'] ?? 0);
        $orchestration = sanitize_key((string) ($mutation['orchestration'] ?? ''));
        if ($job_id === '' && $run_id <= 0) {
            return array('applied' => false, 'reason' => 'missing_job_context');
        }

        $state_key = $this->aysa_technical_site_audit_state_key($project_id);
        $existing = get_option($state_key, array());
        if (!is_array($existing)) {
            $existing = array();
        }

        $is_new_job = ($job_id !== '' && $job_id !== sanitize_text_field((string) ($existing['job_id'] ?? '')))
            || ($run_id > 0 && $run_id !== intval($existing['run_id'] ?? 0));
        $state = array_merge($existing, array(
            'project_id' => $project_id,
            'run_id' => $run_id > 0 ? $run_id : intval($existing['run_id'] ?? 0),
            'job_id' => $job_id,
            'status' => sanitize_key((string) ($mutation['status'] ?? ($existing['status'] ?? 'running'))),
            'target_url' => esc_url_raw((string) ($mutation['target_url'] ?? ($existing['target_url'] ?? ''))),
            'started_at' => sanitize_text_field((string) ($mutation['started_at'] ?? ($existing['started_at'] ?? current_time('mysql')))),
            'updated_at' => current_time('mysql'),
            'provider' => sanitize_text_field((string) ($mutation['provider'] ?? ($existing['provider'] ?? 'aysa_site_audit'))),
            'source' => sanitize_text_field((string) ($mutation['source'] ?? ($existing['source'] ?? 'agent8'))),
            'orchestration' => $orchestration !== '' ? $orchestration : sanitize_key((string) ($existing['orchestration'] ?? 'legacy_polling')),
        ));

        if ($is_new_job) {
            unset($state['kickoff_email_sent_at'], $state['final_email_sent_at'], $state['last_completion_email_status']);
        }

        update_option($state_key, $state, false);
        if ($state['orchestration'] === 'worker_managed') {
            return array(
                'applied' => true,
                'operation' => 'sync_technical_site_audit_state',
                'job_id' => $job_id,
                'run_id' => intval($state['run_id'] ?? 0),
                'status' => (string) ($state['status'] ?? 'queued'),
                'kickoff_email_sent' => false,
            );
        }

        $state = $this->aysa_technical_site_audit_send_kickoff_email_if_needed($project_id, $state);
        $this->aysa_schedule_technical_site_audit_poll($project_id, 120);

        return array(
            'applied' => true,
            'operation' => 'sync_technical_site_audit_state',
            'job_id' => $job_id,
            'run_id' => intval($state['run_id'] ?? 0),
            'status' => (string) ($state['status'] ?? 'running'),
            'kickoff_email_sent' => !empty($state['kickoff_email_sent_at']),
        );
    }

    private function aysa_agent8_sync_technical_pagespeed_state($project_id, $mutation) {
        $project_id = intval($project_id);
        $mutation = is_array($mutation) ? $mutation : array();
        if ($project_id <= 0) {
            return array('applied' => false, 'reason' => 'invalid_project_id');
        }

        $run_id = intval($mutation['run_id'] ?? 0);
        if ($run_id <= 0) {
            return array('applied' => false, 'reason' => 'missing_run_context');
        }

        $state_key = $this->aysa_technical_pagespeed_state_key($project_id);
        $existing = get_option($state_key, array());
        if (!is_array($existing)) {
            $existing = array();
        }

        $state = array_merge($existing, array(
            'project_id' => $project_id,
            'run_id' => $run_id,
            'status' => sanitize_key((string) ($mutation['status'] ?? ($existing['status'] ?? 'queued'))),
            'target_url' => esc_url_raw((string) ($mutation['target_url'] ?? ($existing['target_url'] ?? ''))),
            'started_at' => sanitize_text_field((string) ($mutation['started_at'] ?? ($existing['started_at'] ?? current_time('mysql')))),
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'provider' => sanitize_text_field((string) ($mutation['provider'] ?? ($existing['provider'] ?? 'google_pagespeed_api'))),
            'source' => sanitize_text_field((string) ($mutation['source'] ?? ($existing['source'] ?? 'agent8'))),
            'orchestration' => sanitize_key((string) ($mutation['orchestration'] ?? ($existing['orchestration'] ?? 'worker_managed'))),
            'data' => isset($existing['data']) && is_array($existing['data']) ? $existing['data'] : array(),
        ));

        update_option($state_key, $state, false);

        return array(
            'applied' => true,
            'operation' => 'sync_technical_pagespeed_state',
            'run_id' => intval($state['run_id'] ?? 0),
            'status' => (string) ($state['status'] ?? 'queued'),
        );
    }

    private function aysa_agent8_sync_technical_schema_state($project_id, $mutation) {
        $project_id = intval($project_id);
        $mutation = is_array($mutation) ? $mutation : array();
        if ($project_id <= 0) {
            return array('applied' => false, 'reason' => 'invalid_project_id');
        }

        $run_id = intval($mutation['run_id'] ?? 0);
        if ($run_id <= 0) {
            return array('applied' => false, 'reason' => 'missing_run_context');
        }

        $state_key = $this->aysa_technical_schema_state_key($project_id);
        $existing = get_option($state_key, array());
        if (!is_array($existing)) {
            $existing = array();
        }

        $state = array_merge($existing, array(
            'project_id' => $project_id,
            'run_id' => $run_id,
            'status' => sanitize_key((string) ($mutation['status'] ?? ($existing['status'] ?? 'queued'))),
            'started_at' => sanitize_text_field((string) ($mutation['started_at'] ?? ($existing['started_at'] ?? current_time('mysql')))),
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'provider' => sanitize_text_field((string) ($mutation['provider'] ?? ($existing['provider'] ?? 'wp_local_snapshot'))),
            'source' => sanitize_text_field((string) ($mutation['source'] ?? ($existing['source'] ?? 'agent8'))),
            'orchestration' => sanitize_key((string) ($mutation['orchestration'] ?? ($existing['orchestration'] ?? 'worker_managed'))),
            'schema_markup_snapshot' => isset($existing['schema_markup_snapshot']) && is_array($existing['schema_markup_snapshot'])
                ? $existing['schema_markup_snapshot']
                : array(),
        ));

        update_option($state_key, $state, false);

        return array(
            'applied' => true,
            'operation' => 'sync_technical_schema_state',
            'run_id' => intval($state['run_id'] ?? 0),
            'status' => (string) ($state['status'] ?? 'queued'),
        );
    }

    private function aysa_agent8_sync_technical_redirects_state($project_id, $mutation) {
        $project_id = intval($project_id);
        $mutation = is_array($mutation) ? $mutation : array();
        if ($project_id <= 0) {
            return array('applied' => false, 'reason' => 'invalid_project_id');
        }

        $run_id = intval($mutation['run_id'] ?? 0);
        if ($run_id <= 0) {
            return array('applied' => false, 'reason' => 'missing_run_context');
        }

        $state_key = $this->aysa_technical_redirects_state_key($project_id);
        $existing = get_option($state_key, array());
        if (!is_array($existing)) {
            $existing = array();
        }

        $state = array_merge($existing, array(
            'project_id' => $project_id,
            'run_id' => $run_id,
            'status' => sanitize_key((string) ($mutation['status'] ?? ($existing['status'] ?? 'queued'))),
            'started_at' => sanitize_text_field((string) ($mutation['started_at'] ?? ($existing['started_at'] ?? current_time('mysql')))),
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'provider' => sanitize_text_field((string) ($mutation['provider'] ?? ($existing['provider'] ?? 'wp_local_snapshot'))),
            'source' => sanitize_text_field((string) ($mutation['source'] ?? ($existing['source'] ?? 'agent8'))),
            'orchestration' => sanitize_key((string) ($mutation['orchestration'] ?? ($existing['orchestration'] ?? 'worker_managed'))),
            'snapshot' => isset($existing['snapshot']) && is_array($existing['snapshot']) ? $existing['snapshot'] : array(),
        ));

        update_option($state_key, $state, false);

        return array(
            'applied' => true,
            'operation' => 'sync_technical_redirects_state',
            'run_id' => intval($state['run_id'] ?? 0),
            'status' => (string) ($state['status'] ?? 'queued'),
        );
    }

    private function aysa_agent8_sync_technical_sitemap_robots_state($project_id, $mutation) {
        $project_id = intval($project_id);
        $mutation = is_array($mutation) ? $mutation : array();
        if ($project_id <= 0) {
            return array('applied' => false, 'reason' => 'invalid_project_id');
        }

        $run_id = intval($mutation['run_id'] ?? 0);
        if ($run_id <= 0) {
            return array('applied' => false, 'reason' => 'missing_run_context');
        }

        $state_key = $this->aysa_technical_sitemap_robots_state_key($project_id);
        $existing = get_option($state_key, array());
        if (!is_array($existing)) {
            $existing = array();
        }

        $state = array_merge($existing, array(
            'project_id' => $project_id,
            'run_id' => $run_id,
            'status' => sanitize_key((string) ($mutation['status'] ?? ($existing['status'] ?? 'queued'))),
            'target_url' => esc_url_raw((string) ($mutation['target_url'] ?? ($existing['target_url'] ?? ''))),
            'started_at' => sanitize_text_field((string) ($mutation['started_at'] ?? ($existing['started_at'] ?? current_time('mysql')))),
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'provider' => sanitize_text_field((string) ($mutation['provider'] ?? ($existing['provider'] ?? 'wp_local_snapshot'))),
            'source' => sanitize_text_field((string) ($mutation['source'] ?? ($existing['source'] ?? 'agent8'))),
            'orchestration' => sanitize_key((string) ($mutation['orchestration'] ?? ($existing['orchestration'] ?? 'worker_managed'))),
            'data' => isset($existing['data']) && is_array($existing['data']) ? $existing['data'] : array(),
        ));

        update_option($state_key, $state, false);

        return array(
            'applied' => true,
            'operation' => 'sync_technical_sitemap_robots_state',
            'run_id' => intval($state['run_id'] ?? 0),
            'status' => (string) ($state['status'] ?? 'queued'),
        );
    }

    private function aysa_schedule_technical_site_audit_poll($project_id, $delay_seconds = 120) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return;
        }

        $timestamp = time() + max(30, intval($delay_seconds));
        wp_schedule_single_event($timestamp, 'aysa_technical_site_audit_poll_event', array($project_id));
        if (function_exists('spawn_cron')) {
            spawn_cron();
        }
    }

    private function aysa_technical_site_audit_notification_email($project_id) {
        $project_id = intval($project_id);

        $account_email = sanitize_email((string) get_option('aysa_account_email', ''));
        if ($account_email !== '' && is_email($account_email)) {
            return $account_email;
        }

        $configured_email = sanitize_email((string) get_option('aysa_email_address', ''));
        if ($configured_email !== '' && is_email($configured_email)) {
            return $configured_email;
        }

        $project_general = get_option('aysa_wizard_general_project_' . $project_id, array());
        if (is_array($project_general)) {
            $support_email = sanitize_email((string) ($project_general['support_email'] ?? ''));
            if ($support_email !== '' && is_email($support_email)) {
                return $support_email;
            }
        }

        if (class_exists('Aysa_Google_OAuth_Manager')) {
            try {
                $oauth_manager = Aysa_Google_OAuth_Manager::get_instance();
                if (is_object($oauth_manager) && method_exists($oauth_manager, 'get_status_for_project')) {
                    $oauth_status = $oauth_manager->get_status_for_project($project_id);
                    if (!is_wp_error($oauth_status) && is_array($oauth_status)) {
                        $google_email = sanitize_email((string) ($oauth_status['email'] ?? ''));
                        if ($google_email !== '' && is_email($google_email)) {
                            return $google_email;
                        }
                    }
                }
            } catch (Exception $e) {
                // Fall back to default notification target.
            }
        }

        return $this->get_notification_target_email();
    }

    private function aysa_agent8_communications_table_name() {
        global $wpdb;
        return $wpdb->prefix . 'aysa_agent8_communications';
    }

    private function aysa_agent8_ensure_communications_table() {
        global $wpdb;
        $table = esc_sql($this->aysa_agent8_communications_table_name());
        $charset_collate = $wpdb->get_charset_collate();
        $sql = "CREATE TABLE $table (
            id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
            project_id bigint(20) UNSIGNED NOT NULL,
            conversation_id varchar(128) DEFAULT NULL,
            event_type varchar(80) NOT NULL,
            title varchar(255) NOT NULL,
            message text NOT NULL,
            payload longtext NULL,
            severity varchar(32) DEFAULT 'info',
            importance varchar(32) DEFAULT 'normal',
            channels_required text NULL,
            recipient varchar(191) DEFAULT NULL,
            provider varchar(64) DEFAULT NULL,
            provider_message_id varchar(191) DEFAULT NULL,
            email_status varchar(32) DEFAULT 'not_required',
            in_app_status varchar(32) DEFAULT 'unread',
            last_error text NULL,
            read_at datetime DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY project_created (project_id, created_at),
            KEY project_read (project_id, in_app_status),
            KEY email_status (email_status)
        ) $charset_collate;";
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta($sql);
    }

    private function aysa_agent8v3_maybe_send_technical_seo_notice($response, $payload) {
        if (!is_array($response) || !is_array($payload)) {
            return $response;
        }

        $agent_action = sanitize_key((string) ($payload['action'] ?? $payload['agent_action'] ?? ''));
        if (!in_array($agent_action, array('start_technical_seo_run', 'refresh_technical_seo_status'), true)) {
            return $response;
        }

        $state = isset($response['state']) && is_array($response['state']) ? $response['state'] : array();
        $technical = isset($state['data']['technical_seo']) && is_array($state['data']['technical_seo'])
            ? $state['data']['technical_seo']
            : array();
        $phase = sanitize_key((string) ($state['phase'] ?? ''));
        $status = sanitize_key((string) ($technical['status'] ?? ''));
        $run_id = intval($technical['run_id'] ?? 0);
        $event = '';

        if ($agent_action === 'start_technical_seo_run' && $run_id > 0) {
            $event = 'started';
        }
        if ($agent_action === 'refresh_technical_seo_status' && $phase === 'technical_seo_review' && $status === 'completed' && $run_id > 0) {
            $event = 'completed';
        }
        if ($event === '') {
            return $response;
        }

        $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
        $notice_option = 'aysa_agent8v3_technical_seo_notices_' . max(0, intval($project_id));
        $sent = get_option($notice_option, array());
        if (!is_array($sent)) {
            $sent = array();
        }
        $sent_key = $event . ':' . $run_id;
        if (!empty($sent[$sent_key])) {
            return $response;
        }

        $recipient = $project_id > 0 ? $this->aysa_technical_site_audit_notification_email($project_id) : $this->get_notification_target_email();
        if ($recipient === '' || !is_email($recipient)) {
            if (!isset($response['debug']) || !is_array($response['debug'])) {
                $response['debug'] = array();
            }
            $response['debug']['technical_seo_email_notice'] = array(
                'event' => $event,
                'run_id' => $run_id,
                'sent' => false,
                'reason' => 'missing_recipient',
            );
            return $response;
        }

        $agent8_url = admin_url('admin.php?page=aysa-agent8-v3-lab');
        $site_label = wp_parse_url(home_url('/'), PHP_URL_HOST);
        if (!is_string($site_label) || $site_label === '') {
            $site_label = home_url('/');
        }

        if ($event === 'started') {
            $subject = 'AYSA: Auditul Technical SEO a început';
            $body = "Am început auditul Technical SEO pentru {$site_label}.\n\nAnaliza verifică structura tehnică a site-ului, viteza, schema, redirecturile, sitemap/robots și legăturile interne. Poate dura puțin, iar tu nu trebuie să stai cu pagina deschisă.\n\nNu aplicăm nicio modificare automat. Te anunțăm când rezultatele sunt gata pentru review.\n\nPoți reveni în Agent8 aici:\n{$agent8_url}";
        } else {
            $subject = 'AYSA: Auditul Technical SEO este gata';
            $body = "Auditul Technical SEO pentru {$site_label} este gata pentru review.\n\nAm grupat rezultatele pe priorități, ca să poți verifica mai întâi recomandările critice și importante. Nu aplicăm nicio modificare fără confirmarea ta.\n\nDeschide Agent8 pentru concluzii și următorii pași:\n{$agent8_url}";
        }

        $mail_result = $this->aysa_agent8_send_communication_email($recipient, $subject, $body, array(
            'module' => 'technical_seo',
            'trigger' => 'technical_seo_notice_' . $event,
            'project_id' => $project_id,
            'run_id' => $run_id,
            'action_ref' => 'technical_seo_notice:' . $event . ':' . $run_id,
            'deep_link' => $agent8_url,
            'reply_approval_supported' => false,
        ));
        $sent[$sent_key] = array(
            'sent_at' => current_time('mysql'),
            'ok' => !empty($mail_result['ok']),
        );
        if (count($sent) > 50) {
            $sent = array_slice($sent, -50, null, true);
        }
        update_option($notice_option, $sent, false);

        if (!isset($response['debug']) || !is_array($response['debug'])) {
            $response['debug'] = array();
        }
        $response['debug']['technical_seo_email_notice'] = array(
            'event' => $event,
            'run_id' => $run_id,
            'sent' => !empty($mail_result['ok']),
            'error' => sanitize_text_field((string) ($mail_result['error'] ?? '')),
        );

        return $response;
    }

    private function aysa_agent8_send_communication_email($recipient, $subject, $body, $notification_meta = array()) {
        if (!class_exists('Aysa_API_Config')) {
            require_once AYSA_PLUGIN_DIR . 'includes/config/aysa-api-config.php';
        }
        $secret_key = trim((string) get_option('aysa_user_secret_key', ''));
        if ($secret_key === '') {
            $secret_key = trim((string) get_option('aysa_active_access_key', ''));
        }
        if (!class_exists('Aysa_API_Config') || $secret_key === '') {
            return array('ok' => false, 'error' => 'postmark_api_context_missing', 'provider' => 'PostmarkMailer');
        }

        $html = '<div style="font-family:Arial,sans-serif;font-size:16px;line-height:1.55;color:#1f2937;">' . nl2br(esc_html((string) $body), false) . '</div>';
        $payload = array(
            'secretKey' => $secret_key,
            'domainFromWordpress' => wp_parse_url(home_url(), PHP_URL_HOST),
            'to_email' => sanitize_email((string) $recipient),
            'subject' => sanitize_text_field((string) $subject),
            'text_body' => (string) $body,
            'html_body' => $html,
        );
        if (is_array($notification_meta) && !empty($notification_meta['module']) && !empty($notification_meta['trigger'])) {
            $payload['notification_module'] = sanitize_key((string) $notification_meta['module']);
            $payload['notification_trigger'] = sanitize_key((string) $notification_meta['trigger']);
            $payload['notification_project_id'] = intval($notification_meta['project_id'] ?? 0);
            $payload['notification_website_id'] = intval($notification_meta['website_id'] ?? 0);
            $payload['notification_run_id'] = intval($notification_meta['run_id'] ?? 0);
            $payload['notification_action_ref'] = sanitize_text_field((string) ($notification_meta['action_ref'] ?? ''));
            $payload['notification_deep_link'] = esc_url_raw((string) ($notification_meta['deep_link'] ?? ''));
            $payload['notification_reply_approval_supported'] = !empty($notification_meta['reply_approval_supported']);
            $payload['notification_metadata'] = is_array($notification_meta['metadata'] ?? null) ? $notification_meta['metadata'] : array();
        }

        $response = wp_remote_post(rtrim(Aysa_API_Config::get_base_url(), '/') . '/api/Agent8Communication/sendEmail', array(
            'timeout' => 30,
            'headers' => array('Content-Type' => 'application/json'),
            'body' => wp_json_encode($payload),
        ));
        if (is_wp_error($response)) {
            return array('ok' => false, 'error' => $response->get_error_message(), 'provider' => 'PostmarkMailer');
        }
        $data = json_decode((string) wp_remote_retrieve_body($response), true);
        if (!is_array($data)) {
            return array('ok' => false, 'error' => 'invalid_postmark_api_response', 'provider' => 'PostmarkMailer');
        }
        if (empty($data['provider'])) {
            $data['provider'] = 'PostmarkMailer';
        }
        return $data;
    }

    private function aysa_agent8_create_site_audit_communication_event($project_id, $event_type, $title, $message, $payload = array(), $severity = 'info') {
        global $wpdb;
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array('ok' => false, 'error' => 'missing_project_id');
        }
        $this->aysa_agent8_ensure_communications_table();
        $recipient = $this->aysa_technical_site_audit_notification_email($project_id);
        $email_status = $recipient !== '' && is_email($recipient) ? 'pending' : 'failed';
        $last_error = $email_status === 'failed' ? 'missing_or_invalid_recipient' : '';
        $channels = array('in_app', 'email');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $wpdb->insert($this->aysa_agent8_communications_table_name(), array(
            'project_id' => $project_id,
            'conversation_id' => '',
            'event_type' => sanitize_key((string) $event_type),
            'title' => sanitize_text_field((string) $title),
            'message' => sanitize_textarea_field((string) $message),
            'payload' => wp_json_encode(is_array($payload) ? $payload : array()),
            'severity' => sanitize_key((string) $severity),
            'importance' => 'important',
            'channels_required' => wp_json_encode($channels),
            'recipient' => sanitize_email((string) $recipient),
            'provider' => 'PostmarkMailer',
            'provider_message_id' => '',
            'email_status' => $email_status,
            'in_app_status' => 'unread',
            'last_error' => $last_error,
            'created_at' => current_time('mysql'),
            'updated_at' => current_time('mysql'),
        ));
        $event_id = intval($wpdb->insert_id);
        if ($event_id > 0 && $email_status === 'pending') {
            $email = $this->aysa_agent8_send_communication_email($recipient, $title, $message);
            $email_status = !empty($email['ok']) ? 'sent' : 'failed';
            $last_error = !empty($email['ok']) ? '' : sanitize_text_field((string) ($email['error'] ?? 'postmark_send_failed'));
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->update($this->aysa_agent8_communications_table_name(), array(
                'email_status' => $email_status,
                'provider_message_id' => sanitize_text_field((string) ($email['message_id'] ?? '')),
                'last_error' => $last_error,
                'updated_at' => current_time('mysql'),
            ), array('id' => $event_id));
        }

        return array(
            'ok' => $event_id > 0,
            'event_id' => $event_id,
            'recipient' => $recipient,
            'email_status' => $email_status,
            'last_error' => $last_error,
        );
    }

    private function aysa_technical_site_audit_create_external_communication_event($project_id, $payload = array()) {
        $payload = is_array($payload) ? $payload : array();
        $event_payload = isset($payload['payload']) && is_array($payload['payload']) ? $payload['payload'] : array();
        $event_payload['communication_source'] = 'api_worker_shared_service';
        return $this->aysa_agent8_create_site_audit_communication_event(
            $project_id,
            sanitize_key((string) ($payload['event_type'] ?? 'site_audit_worker_event')),
            sanitize_text_field((string) ($payload['title'] ?? 'Agent8 Site Audit update')),
            sanitize_textarea_field((string) ($payload['message'] ?? 'Site Audit worker update.')),
            $event_payload,
            sanitize_key((string) ($payload['severity'] ?? 'info'))
        );
    }

    private function aysa_technical_site_audit_send_kickoff_email_if_needed($project_id, $state) {
        $project_id = intval($project_id);
        $state = is_array($state) ? $state : array();
        if ($project_id <= 0 || !empty($state['kickoff_email_sent_at'])) {
            return $state;
        }

        $recipient = $this->aysa_technical_site_audit_notification_email($project_id);
        if ($recipient === '' || !is_email($recipient)) {
            return $state;
        }

        $job_id = sanitize_text_field((string) ($state['job_id'] ?? ''));
        $target_url = esc_url_raw((string) ($state['target_url'] ?? home_url('/')));
        $subject = 'AYSA Site Audit started';
        $body_lines = array(
            'AYSA Site Audit has started for your project.',
            '',
            $target_url !== '' ? ('Website: ' . $target_url) : '',
            'In plain terms, we are checking whether Google can crawl the site properly, whether important pages are broken, whether metadata is missing, and whether technical SEO blockers need fixing.',
            'You do not need to wait here. We will update you in Agent 8, and we will send another email when the audit is ready for review.',
            '',
            'Project ID: ' . $project_id,
            'Job ID: ' . $job_id,
            '',
            'Open Agent 8: ' . admin_url('admin.php?page=aysa-agent8-v3-lab'),
        );

        $mail_result = $this->aysa_agent8_send_communication_email(
            $recipient,
            $subject,
            implode("\n", $body_lines),
            array(
                'module' => 'technical_site_audit',
                'trigger' => 'site_audit_started',
                'project_id' => $project_id,
                'action_ref' => $job_id !== '' ? 'site_audit_job:' . $job_id : 'site_audit_project:' . $project_id,
                'deep_link' => admin_url('admin.php?page=aysa-agent8-v3-lab'),
                'reply_approval_supported' => false,
            )
        );
        if (!empty($mail_result['ok'])) {
            $state['kickoff_email_sent_at'] = current_time('mysql');
            update_option($this->aysa_technical_site_audit_state_key($project_id), $state, false);
        }

        return $state;
    }

    private function aysa_technical_site_audit_send_final_email_if_needed($project_id, $state) {
        $project_id = intval($project_id);
        $state = is_array($state) ? $state : array();
        if ($project_id <= 0 || !empty($state['final_email_sent_at'])) {
            return $state;
        }

        $status = sanitize_key((string) ($state['status'] ?? ''));
        if (!in_array($status, array('completed', 'failed'), true)) {
            return $state;
        }

        $recipient = $this->aysa_technical_site_audit_notification_email($project_id);
        if ($recipient === '' || !is_email($recipient)) {
            return $state;
        }

        $audit = isset($state['audit_snapshot']) && is_array($state['audit_snapshot']) ? $state['audit_snapshot'] : array();
        $issues = intval($audit['count'] ?? 0);
        $pages = intval($audit['pages_crawled'] ?? 0);
        $health = intval($audit['site_health'] ?? 0);

        if ($status === 'completed') {
            $subject = 'AYSA Site Audit is ready';
            $body_lines = $this->aysa_technical_site_audit_build_popular_final_email_lines($audit, admin_url('admin.php?page=aysa-agent8-v3-lab'));
        } else {
            $subject = 'AYSA Site Audit finished with an issue';
            $body_lines = array(
                'AYSA Site Audit stopped before a usable review snapshot was produced.',
                '',
                'Status: ' . $status,
                'Error: ' . sanitize_text_field((string) ($state['last_error'] ?? 'unknown')),
                '',
                'Open Agent 8: ' . admin_url('admin.php?page=aysa-agent8-v3-lab'),
            );
        }

        $mail_result = $this->aysa_agent8_send_communication_email(
            $recipient,
            $subject,
            implode("\n", $body_lines),
            array(
                'module' => 'technical_site_audit',
                'trigger' => $status === 'completed' ? 'site_audit_completed' : 'site_audit_failed',
                'project_id' => $project_id,
                'action_ref' => !empty($state['job_id']) ? 'site_audit_job:' . sanitize_text_field((string) $state['job_id']) : 'site_audit_project:' . $project_id,
                'deep_link' => admin_url('admin.php?page=aysa-agent8-v3-lab'),
                'reply_approval_supported' => false,
            )
        );
        if (!empty($mail_result['ok'])) {
            $state['final_email_sent_at'] = current_time('mysql');
            $state['last_completion_email_status'] = $status;
            update_option($this->aysa_technical_site_audit_state_key($project_id), $state, false);
        }

        return $state;
    }

    private function aysa_technical_site_audit_build_popular_final_email_lines($audit, $agent8_url) {
        $audit = is_array($audit) ? $audit : array();
        $issues = intval($audit['count'] ?? 0);
        $pages = intval($audit['pages_crawled'] ?? 0);
        $health = intval($audit['site_health'] ?? 0);
        $top_rows = isset($audit['rows']) && is_array($audit['rows']) ? array_values($audit['rows']) : array();

        $body_lines = array(
            'AYSA Site Audit finished and is ready for review.',
            '',
            'Quick summary:',
            '- Crawled pages: ' . $pages,
            '- Issues found: ' . $issues,
            '- Site health: ' . $health . '%',
            '',
            $this->aysa_technical_site_audit_build_popular_verdict_line($health, $issues),
            '',
            'What is good:',
            '- ' . $this->aysa_technical_site_audit_build_good_line($health, $issues, $pages),
            '',
            'What is not good:',
            '- ' . $this->aysa_technical_site_audit_build_bad_line($top_rows),
            '',
            'What should be done next:',
            '- ' . $this->aysa_technical_site_audit_build_next_step_line($top_rows),
        );

        $issue_lines = array();
        foreach (array_slice($top_rows, 0, 3) as $row) {
            if (!is_array($row)) {
                continue;
            }
            $title = trim((string) ($row['title'] ?? ''));
            if ($title === '') {
                continue;
            }
            $affected = trim((string) ($row['affected'] ?? ''));
            $issue_lines[] = $title . ($affected !== '' ? ' (' . $affected . ')' : '');
        }
        if ($issue_lines !== array()) {
            $body_lines[] = '';
            $body_lines[] = 'Main issues:';
            foreach ($issue_lines as $line) {
                $body_lines[] = '- ' . $line;
            }
        }

        $body_lines[] = '';
        $body_lines[] = 'Open Agent 8: ' . $agent8_url;

        return $body_lines;
    }

    private function aysa_technical_site_audit_build_popular_verdict_line($health, $issues) {
        $health = intval($health);
        $issues = intval($issues);

        if ($health >= 85 && $issues <= 5) {
            return 'Verdict: the technical base looks healthy overall, with only minor cleanup left.';
        }
        if ($health >= 60) {
            return 'Verdict: the site is usable, but technical issues are holding SEO back more than they should.';
        }

        return 'Verdict: there are serious technical issues that can limit crawling, indexing, or visibility.';
    }

    private function aysa_technical_site_audit_build_good_line($health, $issues, $pages) {
        $health = intval($health);
        $issues = intval($issues);
        $pages = intval($pages);

        if ($health >= 85 && $issues <= 5) {
            return 'Google should be able to understand and crawl most of the site without major technical blockers.';
        }
        if ($pages > 0) {
            return 'We now have a full technical snapshot, so the problems are clear and can be prioritized properly.';
        }

        return 'The audit collected enough data to show where the main interventions are needed.';
    }

    private function aysa_technical_site_audit_build_bad_line($rows) {
        $rows = is_array($rows) ? $rows : array();
        $top = isset($rows[0]) && is_array($rows[0]) ? $rows[0] : array();
        $title = trim((string) ($top['title'] ?? ''));
        $affected = trim((string) ($top['affected'] ?? ''));

        if ($title !== '') {
            return $title . ($affected !== '' ? ' affects ' . $affected . '.' : '.');
        }

        return 'There are technical issues that can confuse Google or weaken important pages.';
    }

    private function aysa_technical_site_audit_build_next_step_line($rows) {
        $rows = is_array($rows) ? $rows : array();
        $top = isset($rows[0]) && is_array($rows[0]) ? $rows[0] : array();
        $title = strtolower(trim((string) ($top['title'] ?? '')));

        if ($title !== '') {
            if (strpos($title, 'meta') !== false) {
                return 'Start with missing or duplicated metadata on the most important commercial pages.';
            }
            if (strpos($title, 'internal') !== false || strpos($title, 'inlink') !== false) {
                return 'Strengthen internal linking to important pages so Google can discover and support them better.';
            }
            if (strpos($title, 'h1') !== false || strpos($title, 'h2') !== false) {
                return 'Clean up the heading structure in the templates and keep only useful sections.';
            }
            if (strpos($title, '4xx') !== false || strpos($title, '5xx') !== false || strpos($title, 'broken') !== false) {
                return 'Repair or redirect broken URLs first so they stop wasting traffic and crawl budget.';
            }
            if (strpos($title, 'canonical') !== false) {
                return 'Review canonical tags so the site does not send mixed indexing signals to Google.';
            }
            if (strpos($title, 'noindex') !== false) {
                return 'Review noindex directives and keep blocked only the pages that truly should stay out of Google.';
            }
        }

        return 'Open Site Audit in Agent 8 and fix first the issues that affect the most pages or block Google directly.';
    }

    public function handle_technical_site_audit_poll_event($project_id) {
        $project_id = $this->aysa_resolve_installed_project_id(intval($project_id));
        if ($project_id <= 0) {
            return;
        }

        $state_key = $this->aysa_technical_site_audit_state_key($project_id);
        $state = get_option($state_key, array());
        if (!is_array($state)) {
            $state = array();
        }

        $job_id = sanitize_text_field((string) ($state['job_id'] ?? ''));
        if ($job_id === '') {
            return;
        }

        $status_result = $this->aysa_broker_request('POST', '/v1/technical/screamingfrog/status', array('job_id' => $job_id), 45);
        if (is_wp_error($status_result)) {
            $this->aysa_schedule_technical_site_audit_poll($project_id, 180);
            return;
        }

        if (is_array($status_result)) {
            $state = array_merge($state, array(
                'project_id' => $project_id,
                'job_id' => $job_id,
                'status' => (string) ($status_result['status'] ?? ($state['status'] ?? 'unknown')),
                'updated_at' => current_time('mysql'),
                'job_dir' => (string) ($status_result['job_dir'] ?? ($state['job_dir'] ?? '')),
                'mode' => (string) ($status_result['mode'] ?? ($state['mode'] ?? 'baseline_full')),
                'config_path' => (string) ($status_result['config_path'] ?? ($state['config_path'] ?? '')),
                'export_tabs' => (string) ($status_result['export_tabs'] ?? ($state['export_tabs'] ?? '')),
                'bulk_exports' => (string) ($status_result['bulk_exports'] ?? ($state['bulk_exports'] ?? '')),
                'reports' => (string) ($status_result['reports'] ?? ($state['reports'] ?? '')),
                'artifacts' => isset($status_result['artifacts']) && is_array($status_result['artifacts']) ? $status_result['artifacts'] : array(),
            ));
            update_option($state_key, $state, false);
        }

        $status = sanitize_key((string) ($state['status'] ?? 'unknown'));
        if (in_array($status, array('queued', 'running', 'unknown'), true)) {
            $this->aysa_schedule_technical_site_audit_poll($project_id, 180);
            return;
        }

        if ($status === 'completed') {
            $results = $this->aysa_broker_request('POST', '/v1/technical/screamingfrog/results', array(
                'job_id' => $job_id,
                'preview_rows' => 50,
            ), 60);
            if (is_array($results)) {
                $state = array_merge($state, array(
                    'status' => (string) ($results['status'] ?? $status),
                    'updated_at' => current_time('mysql'),
                    'mode' => (string) ($results['mode'] ?? ($state['mode'] ?? 'baseline_full')),
                    'config_path' => (string) ($results['config_path'] ?? ($state['config_path'] ?? '')),
                    'export_tabs' => (string) ($results['export_tabs'] ?? ($state['export_tabs'] ?? '')),
                    'bulk_exports' => (string) ($results['bulk_exports'] ?? ($state['bulk_exports'] ?? '')),
                    'reports' => (string) ($results['reports'] ?? ($state['reports'] ?? '')),
                    'result_count' => count(isset($results['files']) && is_array($results['files']) ? $results['files'] : array()),
                    'latest_output_dir' => (string) ($results['latest_output_dir'] ?? ''),
                    'audit_snapshot' => isset($results['audit']) && is_array($results['audit']) ? $results['audit'] : array(),
                    'result_files' => isset($results['files']) && is_array($results['files']) ? $results['files'] : array(),
                ));
                $version_id = $this->aysa_sf_site_audit_persist_version($project_id, $state);
                if ($version_id > 0) {
                    $latest_version = $this->aysa_sf_site_audit_get_latest_version_row($project_id);
                    if (is_array($latest_version) && !empty($latest_version)) {
                        $state['version_id'] = intval($latest_version['id'] ?? 0);
                        $state['version_number'] = intval($latest_version['version_number'] ?? 0);
                    }
                }
            }
        }

        update_option($state_key, $state, false);
        $state = $this->aysa_technical_site_audit_send_final_email_if_needed($project_id, $state);
        $this->aysa_project_jobs_finalize(
            $project_id,
            $this->aysa_project_jobs_make_worker_ref('technical_site_audit', 'site_audit', (string) ($state['job_id'] ?? '')),
            (string) ($state['status'] ?? 'failed'),
            isset($state['audit_snapshot']) && is_array($state['audit_snapshot']) ? $state['audit_snapshot'] : $state,
            (string) ($state['last_error'] ?? '')
        );
    }

    private function aysa_agent8_refresh_action_result_after_local_mutations($response_result, $project_id) {
        $response_result = is_array($response_result) ? $response_result : array();
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return $response_result;
        }

        $action_result = isset($response_result['action_result']) && is_array($response_result['action_result'])
            ? $response_result['action_result']
            : array();
        $action_key = sanitize_key((string) ($action_result['action_key'] ?? ''));
        if (in_array($action_key, array(
            'recommend_offpage_link_insertion',
            'preview_offpage_link_insertion_order',
            'send_offpage_link_insertion_approval_email',
            'place_offpage_link_insertion_order',
            'recommend_offpage_seo_text_ads',
            'preview_offpage_seo_text_ads_order',
            'send_offpage_seo_text_ads_approval_email',
            'place_offpage_seo_text_ads_order',
            'reject_offpage_gbp_social_post',
            'retry_offpage_gbp_social_post',
            'run_offpage_backlink_audit',
            'refresh_offpage_backlink_audit_status',
            'generate_offpage_backlink_strategy',
            'refresh_offpage_backlink_strategy_status',
                'generate_offpage_adverlink_article',
                'refresh_offpage_adverlink_article_status',
                'publish_offpage_adverlink_article',
                'queue_offpage_publisher_article_order',
                'preview_offpage_link_building_cart',
            'approve_offpage_link_building_cart',
        ), true)) {
            $local_mutation_result = isset($response_result['local_mutation_result']) && is_array($response_result['local_mutation_result'])
                ? $response_result['local_mutation_result']
                : array();
            if (!empty($local_mutation_result['assistant_message'])) {
                $response_result['assistant_message'] = (string) $local_mutation_result['assistant_message'];
            }
            if (isset($local_mutation_result['suggested_actions']) && is_array($local_mutation_result['suggested_actions'])) {
                $response_result['suggested_actions'] = $local_mutation_result['suggested_actions'];
            }
            if (isset($local_mutation_result['summary']) && is_array($local_mutation_result['summary'])) {
                $action_result['summary'] = array_merge(
                    isset($action_result['summary']) && is_array($action_result['summary']) ? $action_result['summary'] : array(),
                    $local_mutation_result['summary']
                );
            }
            if (isset($local_mutation_result['projection']) && is_array($local_mutation_result['projection'])) {
                $action_result['projection'] = $local_mutation_result['projection'];
            }
            if (!empty($local_mutation_result['payment_link'])) {
                $response_result['suggested_actions'][] = array(
                    'type' => 'open_url',
                    'label' => 'Open payment link',
                    'url' => esc_url_raw((string) $local_mutation_result['payment_link']),
                );
            }
            $action_result['refreshed_runtime_bundle'] = $this->aysa_agent8_offpage_runtime_bundle_for_action($project_id, $action_key);
            $response_result['action_result'] = $action_result;
            return $response_result;
        }
        if ($action_key === 'send_missing_pages_to_plan' || $action_key === 'update_content_strategy_items' || $action_key === 'schedule_publishing_plan') {
            $content_strategy_rows = $this->aysa_agent8_get_content_strategy_rows($project_id);
            $content_strategy_meta_map = $this->aysa_agent8_get_content_strategy_meta_map($project_id);
            $content_strategy_statuses = $this->aysa_agent8_get_content_strategy_status_map($project_id);
            $content_strategy_next = $this->aysa_agent8_get_content_strategy_next_item($project_id);
            $publishing_calendar_rows = $this->aysa_agent8_get_publishing_calendar_rows($project_id);

            $action_result['workspace_snapshot'] = array(
                'content_strategy_rows' => $content_strategy_rows,
                'content_strategy_statuses' => $content_strategy_statuses,
                'content_strategy_next' => $content_strategy_next,
                'publishing_calendar_rows' => $publishing_calendar_rows,
            );
            if ($action_key === 'update_content_strategy_items') {
                $action_result['summary'] = array(
                    'items' => count($content_strategy_rows),
                    'statuses' => $content_strategy_statuses,
                    'next' => $content_strategy_next,
                );
                $action_result['projection'] = array(
                    'type' => 'content_strategy',
                    'summary' => $action_result['summary'],
                );
                $action_result['ui_command'] = $this->aysa_agent8_build_content_strategy_review_command($content_strategy_rows);
            } elseif ($action_key === 'schedule_publishing_plan') {
                $mutation_result = isset($response_result['local_mutation_result']) && is_array($response_result['local_mutation_result'])
                    ? $response_result['local_mutation_result']
                    : array();
                $action_result['summary'] = array(
                    'scheduled' => intval($mutation_result['scheduled'] ?? 0),
                    'queued' => intval($mutation_result['queued'] ?? 0),
                    'items' => count($content_strategy_rows),
                    'statuses' => $content_strategy_statuses,
                    'next' => $content_strategy_next,
                );
                $action_result['projection'] = array(
                    'type' => 'publishing_plan_schedule_result',
                    'summary' => $action_result['summary'],
                    'publishing_calendar' => array(
                        'events' => count($publishing_calendar_rows),
                    ),
                );
            } else {
                $action_result['projection'] = array(
                    'type' => 'plan_mutation_result',
                    'summary' => isset($action_result['summary']) && is_array($action_result['summary']) ? $action_result['summary'] : array(),
                    'content_strategy' => array(
                        'items' => count($content_strategy_rows),
                        'statuses' => $content_strategy_statuses,
                        'next' => $content_strategy_next,
                    ),
                    'publishing_calendar' => array(
                        'events' => count($publishing_calendar_rows),
                    ),
                );
            }
            $action_result['refreshed_runtime_bundle'] = array(
                'content_strategy_rows' => $content_strategy_rows,
                'content_strategy_meta_map' => $content_strategy_meta_map,
                'content_strategy_statuses' => $content_strategy_statuses,
                'content_strategy_next' => $content_strategy_next,
                'publishing_calendar_rows' => $publishing_calendar_rows,
            );

            $response_result['action_result'] = $action_result;
            return $response_result;
        }

        if (in_array($action_key, array(
            'refresh_research_keyword_discovery',
            'refresh_research_competitor_gap',
            'refresh_research_competitor_identification',
            'refresh_research_competitor_backlinks',
            'refresh_research_sitemap_competition',
            'set_offpage_gbp_primary_location',
            'refresh_offpage_gbp_reviews',
            'refresh_offpage_gbp_performance',
            'create_offpage_gbp_post',
            'reply_offpage_gbp_review',
            'upload_offpage_gbp_media',
            'update_offpage_gbp_business_info',
            'update_offpage_gbp_hours',
            'update_offpage_gbp_attributes',
            'update_offpage_gbp_services',
            'accept_offpage_exchange_request',
            'pause_offpage_exchange_request',
            'simulate_accept_offpage_exchange_request',
            'insert_offpage_exchange_partner_box',
            'verify_offpage_exchange_request',
            'approve_offpage_draft_article',
            'accept_onpage_keyword_association',
            'reject_onpage_keyword_association',
            'refresh_technical_pagespeed',
            'email_technical_pagespeed_pdf',
            'refresh_technical_sitemap_robots',
            'unblock_technical_robots',
            'generate_aysa_sitemap',
            'save_technical_robots',
            'restore_recommended_robots',
            'enforce_aysa_sitemap_authority',
            'disable_conflicting_sitemap_providers',
            'preview_technical_internal_linking',
            'apply_technical_internal_linking',
            'apply_technical_schema_recommended',
            'disable_technical_schema',
            'apply_technical_redirect_candidate',
            'mark_technical_gone_candidate',
            'ignore_technical_404_candidate',
            'apply_technical_redirects_batch',
            'mark_technical_gone_batch',
            'ignore_technical_404_batch',
            'refresh_monitoring_rank_tracking',
            'refresh_monitoring_ai_monitoring',
            'refresh_monitoring_keyword_overview',
            'save_monitoring_query_pack',
            'apply_onpage_meta_batch',
            'apply_onpage_rewrite_batch',
            'refresh_offpage_gbp_social_calendar_status',
        ), true)) {
            $local_mutation_result = isset($response_result['local_mutation_result']) && is_array($response_result['local_mutation_result'])
                ? $response_result['local_mutation_result']
                : array();
            $snapshot = isset($local_mutation_result['snapshot']) && is_array($local_mutation_result['snapshot'])
                ? $local_mutation_result['snapshot']
                : array();
            if ($snapshot !== array()) {
                $action_result['workspace_snapshot'] = $snapshot;
                $action_result['summary'] = array_merge(
                    isset($action_result['summary']) && is_array($action_result['summary']) ? $action_result['summary'] : array(),
                    isset($local_mutation_result['summary']) && is_array($local_mutation_result['summary']) ? $local_mutation_result['summary'] : array()
                );
                if (in_array($action_key, array('apply_onpage_meta_batch', 'apply_onpage_rewrite_batch'), true)) {
                    $summary = isset($action_result['summary']) && is_array($action_result['summary']) ? $action_result['summary'] : array();
                    $is_rewrite = $action_key === 'apply_onpage_rewrite_batch';
                    $response_result['assistant_message'] = 'Applied ' . ($is_rewrite ? 'rewrites' : 'meta') . ' to ' . intval($summary['applied_count'] ?? 0)
                        . ' pages out of ' . intval($summary['attempted_count'] ?? ($summary['selected'] ?? 0))
                        . ' attempted. Skipped: ' . intval($summary['skipped_count'] ?? 0)
                        . ', failed: ' . intval($summary['failed_count'] ?? 0)
                        . '. Remaining eligible ' . ($is_rewrite ? 'rewrites' : 'meta') . ': ' . intval($summary['remaining_eligible_count'] ?? 0) . '.';
                }
                if (in_array($action_key, array(
                    'refresh_research_keyword_discovery',
                    'refresh_research_competitor_gap',
                    'refresh_research_competitor_identification',
                    'refresh_research_competitor_backlinks',
                    'refresh_research_sitemap_competition',
                ), true)) {
                    $action_result['refreshed_runtime_bundle'] = array(
                        'research_snapshot' => $this->aysa_adjustments_get_research_snapshot($project_id),
                        'research_sitemap_competition' => $this->aysa_agent8_get_research_sitemap_competition_snapshot($project_id),
                    );
                    if (!empty($local_mutation_result['async_job']) && is_array($local_mutation_result['async_job'])) {
                        $action_result['async_job'] = $local_mutation_result['async_job'];
                    }
                } elseif (in_array($action_key, array(
                    'set_offpage_gbp_primary_location',
                    'refresh_offpage_gbp_reviews',
                    'refresh_offpage_gbp_performance',
                    'create_offpage_gbp_post',
                    'reply_offpage_gbp_review',
                    'upload_offpage_gbp_media',
                    'update_offpage_gbp_business_info',
                    'update_offpage_gbp_hours',
                    'update_offpage_gbp_attributes',
                    'update_offpage_gbp_services',
                    'accept_offpage_exchange_request',
                    'pause_offpage_exchange_request',
                    'simulate_accept_offpage_exchange_request',
                    'insert_offpage_exchange_partner_box',
                    'verify_offpage_exchange_request',
                    'recommend_offpage_link_insertion',
                    'preview_offpage_link_insertion_order',
                    'send_offpage_link_insertion_approval_email',
                    'place_offpage_link_insertion_order',
                    'recommend_offpage_seo_text_ads',
                    'preview_offpage_seo_text_ads_order',
                    'send_offpage_seo_text_ads_approval_email',
                    'place_offpage_seo_text_ads_order',
                    'approve_offpage_draft_article',
                    'refresh_offpage_gbp_social_calendar_status',
                    'reject_offpage_gbp_social_post',
                    'retry_offpage_gbp_social_post',
                ), true)) {
                    $action_result['refreshed_runtime_bundle'] = $this->aysa_agent8_offpage_runtime_bundle_for_action($project_id, $action_key);
                    if (!empty($local_mutation_result['async_job']) && is_array($local_mutation_result['async_job'])) {
                        $action_result['async_job'] = $local_mutation_result['async_job'];
                    }
                } elseif (in_array($action_key, array(
                    'refresh_monitoring_rank_tracking',
                    'refresh_monitoring_ai_monitoring',
                    'refresh_monitoring_keyword_overview',
                    'save_monitoring_query_pack',
                ), true)) {
                    $action_result['refreshed_runtime_bundle'] = array(
                        'monitoring_rank_tracking' => $this->aysa_agent8_get_monitoring_rank_tracking_snapshot($project_id),
                        'monitoring_ai_overview' => $this->aysa_agent8_get_monitoring_ai_overview_snapshot($project_id),
                        'monitoring_query_packs' => $this->aysa_agent8_get_monitoring_query_packs_snapshot($project_id),
                    );
                    if (!empty($local_mutation_result['async_job']) && is_array($local_mutation_result['async_job'])) {
                        $action_result['async_job'] = $local_mutation_result['async_job'];
                    }
                } else {
                    $action_result['refreshed_runtime_bundle'] = array(
                        'technical_snapshot' => $snapshot,
                    );
                }
                $response_result['action_result'] = $action_result;
            }
            return $response_result;
        }

        return $response_result;
    }

    private function aysa_agent8_apply_technical_schema_operation($project_id, array $technical_operation) {
        $project_id = intval($project_id);
        $url = $this->aysa_adjustments_normalize_schema_url((string) ($technical_operation['url'] ?? ''));
        $schema_type = sanitize_key((string) ($technical_operation['schema_type'] ?? 'webpage'));
        $schema_action = sanitize_key((string) ($technical_operation['schema_action'] ?? 'rewrite_recommended'));
        if ($project_id <= 0 || $url === '') {
            return array('applied' => false, 'reason' => 'schema_target_required');
        }

        $item = array(
            'url' => $url,
            'schema_type' => $schema_type !== '' ? $schema_type : 'webpage',
            'primary_recommended_node' => $schema_type !== '' ? $schema_type : 'webpage',
        );
        $result = $this->aysa_technical_schema_run_action($project_id, $schema_action, $item);
        if (is_wp_error($result)) {
            return array(
                'applied' => false,
                'reason' => sanitize_key((string) $result->get_error_code()),
                'message' => (string) $result->get_error_message(),
            );
        }

        return array(
            'applied' => true,
            'operation' => 'apply_technical_schema_recommended',
            'summary' => array(
                'url' => $url,
                'schema_type' => $schema_type,
                'schema_action' => $schema_action,
            ),
            'snapshot' => $this->aysa_adjustments_get_cached_or_idle_technical_schema_snapshot($project_id),
        );
    }

    private function aysa_agent8_disable_technical_schema_operation($project_id, array $technical_operation) {
        $project_id = intval($project_id);
        $url = $this->aysa_adjustments_normalize_schema_url((string) ($technical_operation['url'] ?? ''));
        $schema_type = sanitize_key((string) ($technical_operation['schema_type'] ?? ''));
        if ($project_id <= 0 || $url === '') {
            return array('applied' => false, 'reason' => 'schema_target_required');
        }

        $result = $this->aysa_technical_schema_execute_disable($project_id, $url, $schema_type);
        if (is_wp_error($result)) {
            return array(
                'applied' => false,
                'reason' => sanitize_key((string) $result->get_error_code()),
                'message' => (string) $result->get_error_message(),
            );
        }

        return array(
            'applied' => true,
            'operation' => 'disable_technical_schema',
            'summary' => array(
                'url' => $url,
                'schema_type' => $schema_type,
            ),
            'snapshot' => $this->aysa_adjustments_get_cached_or_idle_technical_schema_snapshot($project_id),
        );
    }

    private function aysa_agent8_apply_technical_redirect_candidate($project_id, array $technical_operation) {
        $project_id = intval($project_id);
        $source = $this->aysa_normalize_redirect_source((string) ($technical_operation['source_url'] ?? ''));
        $target = $this->aysa_normalize_redirect_destination((string) ($technical_operation['target_url'] ?? ''));
        if ($project_id <= 0 || $source === '' || $target === '') {
            return array('applied' => false, 'reason' => 'redirect_target_required');
        }

        $result = $this->aysa_add_htaccess_redirect_rule($source, $target);
        if (empty($result['ok'])) {
            return array(
                'applied' => false,
                'reason' => sanitize_key((string) ($result['code'] ?? 'redirect_apply_failed')),
                'message' => (string) ($result['message'] ?? 'Redirect apply failed.'),
            );
        }

        return array(
            'applied' => empty($result['already_done']),
            'already_done' => !empty($result['already_done']),
            'operation' => 'apply_technical_redirect_candidate',
            'summary' => array(
                'source_url' => $source,
                'target_url' => $target,
                'already_done' => !empty($result['already_done']),
            ),
            'snapshot' => $this->aysa_adjustments_get_technical_errors_snapshot($project_id),
        );
    }

    private function aysa_agent8_apply_onpage_mapping_operation($project_id, array $onpage_operation, $row_action) {
        $project_id = intval($project_id);
        $entity_key = sanitize_text_field((string) ($onpage_operation['entity_key'] ?? ''));
        $row_action = sanitize_key((string) $row_action);
        if ($project_id <= 0 || $entity_key === '' || !in_array($row_action, array('accept_mapping', 'reject_mapping'), true)) {
            return array('applied' => false, 'reason' => 'onpage_mapping_target_required');
        }

        $service = $this->aysa_onpage_workspace_service();
        if (!$service) {
            return array('applied' => false, 'reason' => 'onpage_workspace_service_unavailable');
        }

        $result = $row_action === 'accept_mapping'
            ? $service->accept_mapping_review($project_id, $entity_key)
            : $service->reject_mapping_review($project_id, $entity_key);
        if (!is_array($result) || empty($result['ok'])) {
            return array(
                'applied' => false,
                'reason' => sanitize_key((string) ($result['reason'] ?? 'onpage_mapping_apply_failed')),
                'message' => (string) ($result['message'] ?? 'On-page mapping operation failed.'),
            );
        }
        $snapshot_sync = $this->aysa_agent8_sync_keyword_page_map_snapshot($project_id, 'agent8_onpage_mapping_operation');

        return array(
            'applied' => true,
            'operation' => $row_action === 'accept_mapping' ? 'accept_onpage_keyword_association' : 'reject_onpage_keyword_association',
            'summary' => array(
                'entity_key' => $entity_key,
                'row_action' => $row_action,
            ),
            'snapshot' => $this->aysa_agent8_get_onpage_workspace_snapshot($project_id),
            'keyword_page_map_snapshot_sync' => $snapshot_sync,
        );
    }

    private function aysa_agent8_apply_onpage_entity_operation($project_id, array $onpage_operation, $row_action) {
        $project_id = intval($project_id);
        $entity_key = sanitize_text_field((string) ($onpage_operation['entity_key'] ?? ''));
        $row_action = sanitize_key((string) $row_action);
        if ($project_id <= 0 || $entity_key === '' || !in_array($row_action, array('apply_meta', 'apply_rewrite', 'skip', 'refresh'), true)) {
            return array('applied' => false, 'reason' => 'onpage_entity_target_required');
        }

        $service = $this->aysa_onpage_workspace_service();
        if (!$service) {
            return array('applied' => false, 'reason' => 'onpage_workspace_service_unavailable');
        }

        $result = match ($row_action) {
            'apply_meta' => $service->apply_meta_output($project_id, $entity_key),
            'apply_rewrite' => $service->apply_rewrite_output($project_id, $entity_key),
            'skip' => $service->skip_entity($project_id, $entity_key),
            'refresh' => $service->refresh_entity($project_id, $entity_key),
            default => array('ok' => false, 'error' => 'unsupported_row_action'),
        };
        if (!is_array($result) || empty($result['ok'])) {
            return array(
                'applied' => false,
                'reason' => sanitize_key((string) ($result['error'] ?? 'onpage_entity_apply_failed')),
                'message' => (string) ($result['message'] ?? 'On-page entity operation failed.'),
            );
        }

        return array(
            'applied' => true,
            'operation' => $row_action,
            'summary' => array(
                'entity_key' => $entity_key,
                'row_action' => $row_action,
            ),
            'snapshot' => $this->aysa_agent8_get_onpage_workspace_snapshot($project_id),
        );
    }

    private function aysa_agent8_apply_onpage_batch_operation($project_id, array $onpage_operation, $operation) {
        $project_id = intval($project_id);
        $operation = sanitize_key((string) $operation);
        if ($project_id <= 0 || !in_array($operation, array('apply_meta', 'apply_rewrite'), true)) {
            return array('applied' => false, 'reason' => 'onpage_batch_target_required');
        }

        $service = $this->aysa_onpage_workspace_service();
        if (!$service) {
            return array('applied' => false, 'reason' => 'onpage_workspace_service_unavailable');
        }

        $selection = isset($onpage_operation['selection']) && is_array($onpage_operation['selection'])
            ? array_values(array_filter(array_map('sanitize_text_field', $onpage_operation['selection'])))
            : array();
        $filters = isset($onpage_operation['filters']) && is_array($onpage_operation['filters'])
            ? $onpage_operation['filters']
            : array();
        $entity_group = sanitize_key((string) ($onpage_operation['entity_group'] ?? 'page'));
        $batch_size_raw = $onpage_operation['batch_size'] ?? null;
        $batch_size = is_string($batch_size_raw) && strtolower($batch_size_raw) === 'all'
            ? 'all'
            : max(0, intval($batch_size_raw));
        $batch_mode = sanitize_key((string) ($onpage_operation['batch_mode'] ?? 'first_eligible'));

        $result = $service->batch_apply_operation($project_id, $entity_group, $filters, $selection, $operation, array(
            'batch_size' => $batch_size,
            'batch_mode' => $batch_mode !== '' ? $batch_mode : 'first_eligible',
        ));
        if (!is_array($result) || empty($result['ok'])) {
            return array(
                'applied' => false,
                'reason' => sanitize_key((string) ($result['error'] ?? 'onpage_batch_apply_failed')),
                'message' => (string) ($result['message'] ?? 'On-page batch operation failed.'),
            );
        }

        return array(
            'applied' => true,
            'operation' => $operation,
            'summary' => array(
                'selected' => intval($result['selected'] ?? 0),
                'eligible' => intval($result['eligible'] ?? 0),
                'applied_count' => intval($result['applied'] ?? 0),
                'failed_count' => intval($result['failed'] ?? 0),
                'skipped_count' => intval($result['skipped'] ?? 0),
                'attempted_count' => intval($result['attempted_count'] ?? ($result['selected'] ?? 0)),
                'remaining_eligible_count' => intval($result['remaining_eligible_count'] ?? 0),
            ),
            'snapshot' => $this->aysa_agent8_get_onpage_workspace_snapshot($project_id),
        );
    }

    private function aysa_agent8_apply_offpage_gbp_primary_location($project_id, array $offpage_operation) {
        $project_id = intval($project_id);
        $location_name = sanitize_text_field((string) ($offpage_operation['location_name'] ?? ''));
        if ($project_id <= 0 || $location_name === '') {
            return array('applied' => false, 'reason' => 'gbp_location_required');
        }

        $site_host = wp_parse_url(home_url(), PHP_URL_HOST);
        $site_host = is_string($site_host) ? strtolower(trim($site_host)) : '';
        if ($site_host === '') {
            return array('applied' => false, 'reason' => 'site_host_required');
        }

        $result = $this->aysa_broker_request('POST', '/v1/offpage/gbp/location/set-primary', array(
            'project_id' => $project_id,
            'site_host' => $site_host,
            'location_name' => $location_name,
            'selected_location_name' => sanitize_text_field((string) ($offpage_operation['selected_location_name'] ?? '')),
            'selected_account_name' => sanitize_text_field((string) ($offpage_operation['selected_account_name'] ?? '')),
            'selected_at' => sanitize_text_field((string) ($offpage_operation['selected_at'] ?? current_time('mysql'))),
            'selected_by' => (string) get_current_user_id(),
            'selection_context' => sanitize_key((string) ($offpage_operation['selection_context'] ?? 'agent8_offpage_gbp_location_readiness')),
            'writes_enabled' => false,
            'calls_google_write_endpoint' => false,
            'billing_status' => 'non_billable_location_selection',
        ), 45);
        if (is_wp_error($result)) {
            return array(
                'applied' => false,
                'reason' => 'gbp_primary_location_failed',
                'message' => $this->aysa_humanize_broker_error($result, 'Could not set primary GBP location.'),
            );
        }

        $overview = $this->aysa_broker_request('POST', '/v1/offpage/gbp/overview', array(
            'project_id' => $project_id,
            'site_host' => $site_host,
        ), 60);
        if (!is_wp_error($overview) && is_array($overview)) {
            update_option('aysa_gbp_overview_' . $project_id . '_' . md5($site_host), $overview, false);
        } else {
            $overview = $this->aysa_agent8_get_offpage_gbp_overview_snapshot($project_id);
        }

        return array(
            'applied' => true,
            'operation' => 'set_offpage_gbp_primary_location',
            'summary' => array(
                'location_name' => $location_name,
                'selected_at' => current_time('mysql'),
                'selected_by' => (string) get_current_user_id(),
                'billing_status' => 'non_billable_location_selection',
            ),
            'snapshot' => is_array($overview) ? $overview : array(),
        );
    }

    private function aysa_agent8_apply_offpage_gbp_reviews_refresh($project_id, array $offpage_operation) {
        return $this->aysa_agent8_apply_offpage_gbp_direct_proxy(
            $project_id,
            '/v1/offpage/gbp/reviews/list',
            array(
                'location_name' => sanitize_text_field((string) ($offpage_operation['location_name'] ?? '')),
                'force_refresh' => 1,
            ),
            'refresh_offpage_gbp_reviews'
        );
    }

    private function aysa_agent8_apply_offpage_gbp_performance_refresh($project_id, array $offpage_operation) {
        return $this->aysa_agent8_apply_offpage_gbp_direct_proxy(
            $project_id,
            '/v1/offpage/gbp/performance/get',
            array(
                'location_name' => sanitize_text_field((string) ($offpage_operation['location_name'] ?? '')),
                'force_refresh' => 1,
            ),
            'refresh_offpage_gbp_performance'
        );
    }

    private function aysa_agent8_apply_offpage_gbp_create_post($project_id, array $offpage_operation) {
        $summary = sanitize_textarea_field((string) ($offpage_operation['summary'] ?? ''));
        if ($summary === '') {
            return array('applied' => false, 'reason' => 'gbp_post_summary_required');
        }

        return $this->aysa_agent8_apply_offpage_gbp_direct_proxy(
            $project_id,
            '/v1/offpage/gbp/posts/create',
            array(
                'location_name' => sanitize_text_field((string) ($offpage_operation['location_name'] ?? '')),
                'post' => array(
                    'summary' => $summary,
                ),
            ),
            'create_offpage_gbp_post',
            array(
                'post_preview' => $summary,
            )
        );
    }

    private function aysa_agent8_apply_offpage_gbp_reply_review($project_id, array $offpage_operation) {
        $review_name = sanitize_text_field((string) ($offpage_operation['review_name'] ?? ''));
        $comment = sanitize_textarea_field((string) ($offpage_operation['comment'] ?? ''));
        if ($review_name === '' || $comment === '') {
            return array('applied' => false, 'reason' => $review_name === '' ? 'review_name_required' : 'review_comment_required');
        }

        return $this->aysa_agent8_apply_offpage_gbp_direct_proxy(
            $project_id,
            '/v1/offpage/gbp/reviews/reply',
            array(
                'review_name' => $review_name,
                'comment' => $comment,
            ),
            'reply_offpage_gbp_review',
            array(
                'review_name' => $review_name,
            )
        );
    }

    private function aysa_agent8_apply_offpage_gbp_upload_media($project_id, array $offpage_operation) {
        $media_url = esc_url_raw((string) ($offpage_operation['media_url'] ?? ''));
        if ($media_url === '') {
            return array('applied' => false, 'reason' => 'media_url_required');
        }

        return $this->aysa_agent8_apply_offpage_gbp_direct_proxy(
            $project_id,
            '/v1/offpage/gbp/media/upload',
            array(
                'location_name' => sanitize_text_field((string) ($offpage_operation['location_name'] ?? '')),
                'media' => array(
                    'sourceUrl' => $media_url,
                ),
            ),
            'upload_offpage_gbp_media',
            array(
                'media_url' => $media_url,
            )
        );
    }

    private function aysa_agent8_apply_offpage_gbp_update_business_info($project_id, array $offpage_operation) {
        $patch = isset($offpage_operation['patch']) && is_array($offpage_operation['patch']) ? $offpage_operation['patch'] : array();
        if ($patch === array()) {
            return array('applied' => false, 'reason' => 'gbp_patch_required');
        }

        return $this->aysa_agent8_apply_offpage_gbp_direct_proxy(
            $project_id,
            '/v1/offpage/gbp/location/update',
            array(
                'location_name' => sanitize_text_field((string) ($offpage_operation['location_name'] ?? '')),
                'patch' => $patch,
            ),
            'update_offpage_gbp_business_info'
        );
    }

    private function aysa_agent8_apply_offpage_gbp_update_hours($project_id, array $offpage_operation) {
        $regular = isset($offpage_operation['regularHours']) && is_array($offpage_operation['regularHours']) ? $offpage_operation['regularHours'] : array();
        $special = isset($offpage_operation['specialHours']) && is_array($offpage_operation['specialHours']) ? $offpage_operation['specialHours'] : array();
        if ($regular === array() && $special === array()) {
            return array('applied' => false, 'reason' => 'gbp_hours_required');
        }

        return $this->aysa_agent8_apply_offpage_gbp_direct_proxy(
            $project_id,
            '/v1/offpage/gbp/location/hours',
            array(
                'location_name' => sanitize_text_field((string) ($offpage_operation['location_name'] ?? '')),
                'regularHours' => $regular,
                'specialHours' => $special,
            ),
            'update_offpage_gbp_hours'
        );
    }

    private function aysa_agent8_apply_offpage_gbp_update_attributes($project_id, array $offpage_operation) {
        $attributes = isset($offpage_operation['attributes']) && is_array($offpage_operation['attributes']) ? array_values($offpage_operation['attributes']) : array();
        if ($attributes === array()) {
            return array('applied' => false, 'reason' => 'gbp_attributes_required');
        }

        return $this->aysa_agent8_apply_offpage_gbp_direct_proxy(
            $project_id,
            '/v1/offpage/gbp/location/attributes',
            array(
                'location_name' => sanitize_text_field((string) ($offpage_operation['location_name'] ?? '')),
                'attributes' => $attributes,
            ),
            'update_offpage_gbp_attributes',
            array(
                'count' => count($attributes),
            )
        );
    }

    private function aysa_agent8_apply_offpage_gbp_update_services($project_id, array $offpage_operation) {
        $service_items = isset($offpage_operation['serviceItems']) && is_array($offpage_operation['serviceItems']) ? array_values($offpage_operation['serviceItems']) : array();
        if ($service_items === array()) {
            return array('applied' => false, 'reason' => 'gbp_services_required');
        }

        return $this->aysa_agent8_apply_offpage_gbp_direct_proxy(
            $project_id,
            '/v1/offpage/gbp/location/services',
            array(
                'location_name' => sanitize_text_field((string) ($offpage_operation['location_name'] ?? '')),
                'serviceItems' => $service_items,
            ),
            'update_offpage_gbp_services',
            array(
                'count' => count($service_items),
            )
        );
    }

    private function aysa_agent8_apply_offpage_gbp_direct_proxy($project_id, $route, array $extra_payload, $operation, array $extra_summary = array()) {
        $project_id = intval($project_id);
        $route = (string) $route;
        $operation = sanitize_key((string) $operation);
        $location_name = sanitize_text_field((string) ($extra_payload['location_name'] ?? ''));
        if ($project_id <= 0 || $route === '' || $location_name === '') {
            return array('applied' => false, 'reason' => 'gbp_location_required');
        }

        $site_host = wp_parse_url(home_url(), PHP_URL_HOST);
        $site_host = is_string($site_host) ? strtolower(trim($site_host)) : '';
        if ($site_host === '') {
            return array('applied' => false, 'reason' => 'site_host_required');
        }

        $result = $this->aysa_broker_request('POST', $route, array_merge(array(
            'project_id' => $project_id,
            'site_host' => $site_host,
        ), $extra_payload), 60);
        if (is_wp_error($result)) {
            return array(
                'applied' => false,
                'reason' => $operation . '_failed',
                'message' => $this->aysa_humanize_broker_error($result, 'Could not execute GBP action.'),
            );
        }

        $overview = $this->aysa_broker_request('POST', '/v1/offpage/gbp/overview', array(
            'project_id' => $project_id,
            'site_host' => $site_host,
        ), 60);
        if (!is_wp_error($overview) && is_array($overview)) {
            update_option('aysa_gbp_overview_' . $project_id . '_' . md5($site_host), $overview, false);
        } else {
            $overview = $this->aysa_agent8_get_offpage_gbp_overview_snapshot($project_id);
        }

        return array(
            'applied' => true,
            'operation' => $operation,
            'summary' => array_merge(array(
                'location_name' => $location_name,
            ), $extra_summary),
            'snapshot' => is_array($overview) ? $overview : array(),
        );
    }

    private function aysa_agent8_send_offpage_draft_article_approval_email($project_id, array $offpage_operation) {
        $draft_id = intval($offpage_operation['draft_id'] ?? 0);
        if ($draft_id <= 0) {
            return array('applied' => false, 'reason' => 'missing_draft_id');
        }

        $draft = array();
        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/publisher-articles/get', array(
            'draft_id' => $draft_id,
        ), 60);
        if (!is_wp_error($broker_result) && isset($broker_result['draft']) && is_array($broker_result['draft'])) {
            $draft = $broker_result['draft'];
        }

        $current_user = wp_get_current_user();
        $recipient = ($current_user && !empty($current_user->user_email)) ? sanitize_email((string) $current_user->user_email) : '';
        if ($recipient === '' || !is_email($recipient)) {
            $recipient = sanitize_email((string) get_option('admin_email', ''));
        }
        if ($recipient === '' || !is_email($recipient)) {
            return array('applied' => false, 'reason' => 'missing_recipient');
        }

        $title = sanitize_text_field((string) ($draft['title'] ?? 'Adverlink article draft'));
        $excerpt = trim(wp_strip_all_tags((string) ($draft['excerpt'] ?? '')));
        $portal_domain = sanitize_text_field((string) ($offpage_operation['portal_domain'] ?? ($draft['portal_domain'] ?? '')));
        $target_url = esc_url_raw((string) ($offpage_operation['target_url'] ?? ($draft['target_url'] ?? '')));
        $order_ref = sanitize_text_field((string) ($offpage_operation['order_ref'] ?? ($draft['order_ref'] ?? '')));
        $agent8_url = admin_url('admin.php?page=aysa-agent8-v3-lab');

        $subject = 'Article ready for approval: ' . $title;
        $body_lines = array(
            'An Adverlink article is ready for your review in Agent8.',
            '',
            'Article title: ' . $title,
            'Portal/domain: ' . ($portal_domain !== '' ? $portal_domain : '-'),
            'Target URL: ' . ($target_url !== '' ? $target_url : '-'),
            'Order ref: ' . ($order_ref !== '' ? $order_ref : '-'),
            'Draft ID: ' . $draft_id,
        );
        if ($excerpt !== '') {
            $body_lines[] = '';
            $body_lines[] = 'Short summary:';
            $body_lines[] = $excerpt;
        }
        $body_lines[] = '';
        $body_lines[] = 'Open Agent8 to preview and approve the article:';
        $body_lines[] = $agent8_url;
        $body_lines[] = '';
        $body_lines[] = 'Important: this email does not approve or publish anything. Approval must happen inside Agent8/admin. Approving this changes Adverlink state and may cost credits.';

        $email = $this->aysa_agent8_send_communication_email($recipient, $subject, implode("\n", $body_lines), array(
            'module' => 'offpage',
            'trigger' => 'offpage_draft_article_approval_requested',
            'project_id' => $project_id,
            'action_ref' => 'offpage_draft_article:' . $draft_id,
            'deep_link' => $agent8_url,
            'reply_approval_supported' => false,
            'metadata' => array(
                'draft_id' => $draft_id,
                'portal_domain' => $portal_domain,
                'target_url' => $target_url,
                'order_ref' => $order_ref,
            ),
        ));
        if (empty($email['ok'])) {
            return array(
                'applied' => false,
                'reason' => 'email_failed',
                'provider' => $email['provider'] ?? 'PostmarkMailer',
                'error' => $email['error'] ?? 'unknown_error',
            );
        }

        return array(
            'applied' => true,
            'operation' => 'send_offpage_draft_article_approval_email',
            'draft_id' => $draft_id,
            'recipient' => $recipient,
            'provider' => $email['provider'] ?? 'PostmarkMailer',
        );
    }

    private function aysa_agent8_offpage_purchase_setup_user_id() {
        $setup_user_id = intval(get_option('aysa_user_id', 0));
        if ($setup_user_id <= 0) {
            $setup_user_id = intval($this->get_user_id());
        }
        if ($setup_user_id <= 0) {
            $setup_user_id = intval($this->aysa_get_authenticated_external_user_id());
        }
        if ($setup_user_id <= 0) {
            $setup_user_id = get_current_user_id();
        }
        return intval($setup_user_id);
    }

    private function aysa_agent8_recommend_offpage_purchase($project_id, array $offpage_operation, $flow) {
        $language = sanitize_key((string) ($offpage_operation['language'] ?? 'en'));
        $setup_user_id = $this->aysa_agent8_offpage_purchase_setup_user_id();
        $legacy = $flow === 'seo_text_ads'
            ? $this->aysa_adjustments_recommend_seo_text_ads_portals($project_id, $setup_user_id, $language)
            : $this->aysa_adjustments_recommend_link_insertion_portals($project_id, $setup_user_id, $language);
        $candidates = isset($legacy['state_meta']['offpage_purchase_candidates']) && is_array($legacy['state_meta']['offpage_purchase_candidates'])
            ? array_values($legacy['state_meta']['offpage_purchase_candidates'])
            : array();
        $keyword = sanitize_text_field((string) ($legacy['state_meta']['offpage_purchase_keyword'] ?? ''));
        $suggested_actions = array();
        foreach (array_slice($candidates, 0, 3) as $candidate) {
            if (!is_array($candidate)) {
                continue;
            }
            $payload = $this->aysa_agent8_normalize_offpage_purchase_payload($candidate, $flow, $keyword);
            if (empty($payload['portal_id']) || empty($payload['portal_domain'])) {
                continue;
            }
            $suggested_actions[] = array(
                'type' => 'action',
                'action_key' => $flow === 'seo_text_ads' ? 'preview_offpage_seo_text_ads_order' : 'preview_offpage_link_insertion_order',
                'label' => 'Preview ' . $payload['portal_domain'],
                'action_payload' => $payload,
            );
        }

        return array(
            'applied' => true,
            'operation' => 'recommend_offpage_' . $flow,
            'assistant_message' => (string) ($legacy['assistant_message'] ?? ''),
            'suggested_actions' => $suggested_actions,
            'summary' => array(
                'flow' => $flow,
                'candidates' => count($candidates),
            ),
            'projection' => array(
                'type' => $flow === 'seo_text_ads' ? 'offpage_seo_text_ads' : 'offpage_link_insertion',
                'title' => $flow === 'seo_text_ads' ? 'SEO Text Ads' : 'Link Insertion',
                'summary' => array(
                    'status' => 'recommendations_ready',
                    'candidates' => array_slice(array_map(function ($candidate) use ($flow, $keyword) {
                        return is_array($candidate) ? $this->aysa_agent8_normalize_offpage_purchase_payload($candidate, $flow, $keyword) : array();
                    }, $candidates), 0, 3),
                ),
            ),
        );
    }

    private function aysa_agent8_preview_offpage_purchase($project_id, array $offpage_operation, $flow) {
        $payload = $this->aysa_agent8_normalize_offpage_purchase_payload($offpage_operation, $flow, sanitize_text_field((string) ($offpage_operation['keyword'] ?? '')));
        if (empty($payload['portal_id']) || empty($payload['portal_domain'])) {
            return array('applied' => false, 'reason' => 'missing_purchase_candidate');
        }
        return array(
            'applied' => true,
            'operation' => 'preview_offpage_' . $flow . '_order',
            'summary' => $payload,
            'projection' => array(
                'type' => $flow === 'seo_text_ads' ? 'offpage_seo_text_ads' : 'offpage_link_insertion',
                'title' => $flow === 'seo_text_ads' ? 'SEO Text Ads' : 'Link Insertion',
                'summary' => array_merge($payload, array('status' => 'preview_ready')),
            ),
        );
    }

    private function aysa_agent8_send_offpage_purchase_approval_email($project_id, array $offpage_operation, $flow) {
        $payload = $this->aysa_agent8_normalize_offpage_purchase_payload($offpage_operation, $flow, sanitize_text_field((string) ($offpage_operation['keyword'] ?? '')));
        if (empty($payload['portal_domain'])) {
            return array('applied' => false, 'reason' => 'missing_purchase_candidate');
        }

        $current_user = wp_get_current_user();
        $recipient = ($current_user && !empty($current_user->user_email)) ? sanitize_email((string) $current_user->user_email) : '';
        if ($recipient === '' || !is_email($recipient)) {
            $recipient = sanitize_email((string) get_option('admin_email', ''));
        }
        if ($recipient === '' || !is_email($recipient)) {
            return array('applied' => false, 'reason' => 'missing_recipient');
        }

        $label = $flow === 'seo_text_ads' ? 'SEO Text Ads' : 'Link Insertion';
        $cost = intval($flow === 'seo_text_ads' ? ($payload['monthly_cost_cents'] ?? 0) : ($payload['estimated_cost_cents'] ?? 0));
        $billing = $flow === 'seo_text_ads' ? 'monthly' : 'one-time';
        $agent8_url = admin_url('admin.php?page=aysa-agent8-v3-lab');
        $subject = $label . ' offer ready for approval: ' . $payload['portal_domain'];
        $body_lines = array(
            'An off-page placement offer is ready for your review in Agent8.',
            '',
            'Service: ' . $label,
            'Portal/domain: ' . ($payload['portal_domain'] !== '' ? $payload['portal_domain'] : '-'),
            'Target URL: ' . ($payload['target_url'] !== '' ? $payload['target_url'] : '-'),
            'Keyword/anchor: ' . ($payload['keyword'] !== '' ? $payload['keyword'] : '-'),
            'Billing model: ' . $billing,
            'Estimated cost: ' . $cost . ' credits',
            '',
            'Open Agent8 to preview and approve the order:',
            $agent8_url,
            '',
            'Important: this email does not create an order and does not consume credits. Placing the order must happen inside Agent8/admin and changes Adverlink state.',
        );

        $email = $this->aysa_agent8_send_communication_email($recipient, $subject, implode("\n", $body_lines), array(
            'module' => 'offpage',
            'trigger' => 'offpage_purchase_approval_requested',
            'project_id' => $project_id,
            'action_ref' => 'offpage_purchase:' . $flow . ':' . sanitize_key((string) ($payload['portal_id'] ?? $payload['portal_domain'] ?? 'unknown')) . ':' . md5((string) ($payload['target_url'] ?? '')),
            'deep_link' => $agent8_url,
            'reply_approval_supported' => false,
            'metadata' => array(
                'flow' => $flow,
                'portal_domain' => (string) ($payload['portal_domain'] ?? ''),
                'target_url' => (string) ($payload['target_url'] ?? ''),
                'keyword' => (string) ($payload['keyword'] ?? ''),
                'cost_credits' => $cost,
                'billing_model' => $billing,
            ),
        ));
        if (empty($email['ok'])) {
            return array(
                'applied' => false,
                'reason' => 'email_failed',
                'provider' => $email['provider'] ?? 'PostmarkMailer',
                'error' => $email['error'] ?? 'unknown_error',
            );
        }

        return array(
            'applied' => true,
            'operation' => 'send_offpage_' . $flow . '_approval_email',
            'recipient' => $recipient,
            'provider' => $email['provider'] ?? 'PostmarkMailer',
            'summary' => array_merge($payload, array('status' => 'approval_email_sent')),
        );
    }

    private function aysa_agent8_place_offpage_purchase_order($project_id, array $offpage_operation, $flow) {
        $language = sanitize_key((string) ($offpage_operation['language'] ?? 'en'));
        $setup_user_id = $this->aysa_agent8_offpage_purchase_setup_user_id();
        $payload = $this->aysa_agent8_normalize_offpage_purchase_payload($offpage_operation, $flow, sanitize_text_field((string) ($offpage_operation['keyword'] ?? '')));
        $legacy = $flow === 'seo_text_ads'
            ? $this->aysa_adjustments_place_seo_text_ads_order($project_id, $setup_user_id, $payload, $language)
            : $this->aysa_adjustments_place_link_insertion_order($project_id, $setup_user_id, $payload, $language);

        return array(
            'applied' => true,
            'operation' => 'place_offpage_' . $flow . '_order',
            'assistant_message' => (string) ($legacy['assistant_message'] ?? ''),
            'payment_link' => isset($legacy['payment_link']) ? esc_url_raw((string) $legacy['payment_link']) : '',
            'required_credits' => intval($legacy['required_credits'] ?? 0),
            'available_credits' => intval($legacy['available_credits'] ?? 0),
            'missing_credits' => intval($legacy['missing_credits'] ?? 0),
            'suggested_actions' => !empty($legacy['payment_link']) ? array(
                array(
                    'type' => 'open_url',
                    'label' => 'Open payment link',
                    'url' => esc_url_raw((string) $legacy['payment_link']),
                ),
            ) : array(),
            'summary' => array_merge($payload, array(
                'status' => !empty($legacy['payment_link']) ? 'payment_required' : 'order_submitted',
                'payment_link' => isset($legacy['payment_link']) ? esc_url_raw((string) $legacy['payment_link']) : '',
            )),
            'projection' => array(
                'type' => $flow === 'seo_text_ads' ? 'offpage_seo_text_ads' : 'offpage_link_insertion',
                'title' => $flow === 'seo_text_ads' ? 'SEO Text Ads' : 'Link Insertion',
                'summary' => array(
                    'status' => !empty($legacy['payment_link']) ? 'payment_required' : 'order_submitted',
                    'payment_link' => isset($legacy['payment_link']) ? esc_url_raw((string) $legacy['payment_link']) : '',
                ),
            ),
        );
    }

    private function aysa_agent8_normalize_offpage_purchase_payload(array $source, $flow, $keyword) {
        $flow = $flow === 'seo_text_ads' ? 'seo_text_ads' : 'link_insertion';
        $keyword = sanitize_text_field((string) ($source['keyword'] ?? $keyword));
        $cost_key = $flow === 'seo_text_ads' ? 'monthly_cost_cents' : 'estimated_cost_cents';
        $cost = max(0, intval($source[$cost_key] ?? $source['estimated_cost_cents'] ?? $source['monthly_cost_cents'] ?? 0));
        return array(
            'portal_id' => sanitize_text_field((string) ($source['portal_id'] ?? $source['id'] ?? '')),
            'portal_domain' => sanitize_text_field((string) ($source['portal_domain'] ?? $source['domain'] ?? '')),
            'domain' => sanitize_text_field((string) ($source['portal_domain'] ?? $source['domain'] ?? '')),
            'authority' => floatval($source['authority'] ?? 0),
            'score' => floatval($source['score'] ?? 0),
            'service_type' => $flow === 'seo_text_ads' ? 'seo_ads_text' : 'link_insertion',
            'billing_type' => $flow === 'seo_text_ads' ? 'monthly' : 'one_time',
            'target_url' => esc_url_raw((string) ($source['target_url'] ?? home_url('/'))),
            'keyword' => $keyword,
            'estimated_cost_cents' => $flow === 'seo_text_ads' ? 0 : $cost,
            'monthly_cost_cents' => $flow === 'seo_text_ads' ? $cost : 0,
            'included_link_eligible' => isset($source['included_link_eligible']) ? (bool) $source['included_link_eligible'] : null,
        );
    }

    private function aysa_agent8_run_offpage_operation_now($project_id, $task, array $input) {
        $project_id = intval($project_id);
        $task = sanitize_key((string) $task);
        if ($project_id <= 0 || $task === '' || !in_array($task, $this->aysa_offpage_async_supported_tasks(), true)) {
            return array('applied' => false, 'reason' => 'invalid_offpage_async_task');
        }

        $request = $this->aysa_offpage_async_prepare_request($task, array_merge($input, array(
            'project_id' => $project_id,
        )));
        if (is_wp_error($request)) {
            return array(
                'applied' => false,
                'reason' => sanitize_key((string) $request->get_error_code()),
                'message' => (string) $request->get_error_message(),
            );
        }
        if (!empty($input['suppress_billing'])) {
            $request['suppress_billing'] = true;
        }

        $result = $this->aysa_offpage_async_execute($request);
        if (is_wp_error($result)) {
            return array(
                'applied' => false,
                'operation' => $task,
                'reason' => sanitize_key((string) $result->get_error_code()),
                'message' => (string) $result->get_error_message(),
                'snapshot' => $this->aysa_agent8_offpage_snapshot_for_task($project_id, $task),
            );
        }

        return array(
            'applied' => true,
            'operation' => $task,
            'summary' => array('status' => 'completed'),
            'result' => is_array($result) ? $result : array(),
            'snapshot' => $this->aysa_agent8_offpage_snapshot_for_task($project_id, $task),
        );
    }

    private function aysa_agent8_start_offpage_async_operation($project_id, $task, array $input) {
        $project_id = intval($project_id);
        $task = sanitize_key((string) $task);
        if ($project_id <= 0 || $task === '' || !in_array($task, $this->aysa_offpage_async_supported_tasks(), true)) {
            return array('applied' => false, 'reason' => 'invalid_offpage_async_task');
        }

        $request = $this->aysa_offpage_async_prepare_request($task, array_merge($input, array(
            'project_id' => $project_id,
        )));
        if (is_wp_error($request)) {
            return array(
                'applied' => false,
                'reason' => sanitize_key((string) $request->get_error_code()),
                'message' => (string) $request->get_error_message(),
            );
        }

        $request_hash = (string) ($request['request_hash'] ?? '');
        $existing = $this->aysa_offpage_async_get_status($project_id, $task);
        if (
            is_array($existing) &&
            !empty($existing['run_id']) &&
            in_array((string) ($existing['status'] ?? ''), array('queued', 'running'), true) &&
            (string) ($existing['request_hash'] ?? '') === $request_hash
        ) {
            return array(
                'applied' => true,
                'operation' => $task,
                'summary' => array(
                    'status' => (string) ($existing['status'] ?? 'queued'),
                    'run_id' => (string) ($existing['run_id'] ?? ''),
                ),
                'snapshot' => $this->aysa_agent8_offpage_snapshot_for_task($project_id, $task),
                'async_job' => $existing,
            );
        }

        $run_id = uniqid('offpage_', true);
        $status = array(
            'task' => $task,
            'project_id' => $project_id,
            'run_id' => $run_id,
            'request_hash' => $request_hash,
            'status' => 'queued',
            'result' => null,
            'error' => '',
            'started_at' => time(),
            'updated_at' => time(),
            'finished_at' => 0,
        );
        $this->aysa_offpage_async_save_status($project_id, $task, $status);
        $this->aysa_offpage_async_schedule_event(array(
            'task' => $task,
            'project_id' => $project_id,
            'run_id' => $run_id,
            'request_hash' => $request_hash,
            'request' => $request,
        ), 1);

        return array(
            'applied' => true,
            'operation' => $task,
            'summary' => array(
                'status' => 'queued',
                'run_id' => $run_id,
            ),
            'snapshot' => $this->aysa_agent8_offpage_snapshot_for_task($project_id, $task),
            'async_job' => $status,
        );
    }

    private function aysa_agent8_approve_offpage_draft_article($project_id, array $input) {
        $project_id = intval($project_id);
        $draft_id = intval($input['draft_id'] ?? 0);
        if ($project_id <= 0 || $draft_id <= 0) {
            return array('applied' => false, 'reason' => 'invalid_draft_id');
        }

        $reference = 'offpage_approve_publisher_article_draft_' . $draft_id;
        if ($this->aysa_wallet_usage_reference_exists($project_id, $reference) || $this->aysa_agent8_is_publisher_draft_approved($project_id, $draft_id)) {
            $this->aysa_agent8_mark_publisher_draft_approved($project_id, $draft_id);
            return array(
                'applied' => true,
                'operation' => 'publisher_draft_approve',
                'summary' => array(
                    'status' => 'already_approved',
                    'draft_id' => $draft_id,
                ),
                'snapshot' => $this->aysa_agent8_get_offpage_draft_articles_snapshot($project_id),
            );
        }

        $request_hash = md5($project_id . '|approve|' . $draft_id);
        $existing = $this->aysa_offpage_async_get_status($project_id, 'publisher_draft_approve');
        if (
            is_array($existing) &&
            !empty($existing['run_id']) &&
            in_array((string) ($existing['status'] ?? ''), array('queued', 'running'), true) &&
            (string) ($existing['request_hash'] ?? '') === $request_hash
        ) {
            return array(
                'applied' => true,
                'operation' => 'publisher_draft_approve',
                'summary' => array(
                    'status' => 'already_queued',
                    'run_id' => (string) ($existing['run_id'] ?? ''),
                ),
                'snapshot' => $this->aysa_agent8_get_offpage_draft_articles_snapshot($project_id),
                'async_job' => $existing,
            );
        }

        return $this->aysa_agent8_start_offpage_async_operation($project_id, 'publisher_draft_approve', array(
            'draft_id' => $draft_id,
        ));
    }

    private function aysa_agent8_is_publisher_draft_approved($project_id, $draft_id) {
        $project_id = intval($project_id);
        $draft_id = intval($draft_id);
        if ($project_id <= 0 || $draft_id <= 0) {
            return false;
        }

        foreach ($this->aysa_get_adverlink_orders($project_id) as $order) {
            if (!is_array($order) || intval($order['publisher_article_draft_id'] ?? 0) !== $draft_id) {
                continue;
            }
            $draft_status = strtoupper(trim((string) ($order['publisher_article_draft_status'] ?? '')));
            if ($draft_status === 'APPROVED') {
                return true;
            }
        }

        $status = $this->aysa_offpage_async_get_status($project_id, 'publisher_draft_approve');
        if (!is_array($status) || (string) ($status['status'] ?? '') !== 'completed') {
            return false;
        }
        $request_hash = md5($project_id . '|approve|' . $draft_id);
        if ((string) ($status['request_hash'] ?? '') !== $request_hash) {
            return false;
        }
        $result = isset($status['result']) && is_array($status['result']) ? $status['result'] : array();
        $approved_status = strtoupper($this->aysa_find_first_string_by_keys_recursive($result, array('status', 'draft_status')));
        return $approved_status === 'APPROVED';
    }

    private function aysa_agent8_mark_publisher_draft_approved($project_id, $draft_id, array $draft = array()) {
        $project_id = intval($project_id);
        $draft_id = intval($draft_id);
        if ($project_id <= 0 || $draft_id <= 0) {
            return;
        }

        $orders = $this->aysa_get_adverlink_orders($project_id);
        $changed = false;
        foreach ($orders as $idx => $order) {
            if (!is_array($order) || intval($order['publisher_article_draft_id'] ?? 0) !== $draft_id) {
                continue;
            }
            $orders[$idx]['publisher_article_draft_status'] = 'approved';
            $orders[$idx]['publisher_article_draft_updated_at'] = sanitize_text_field((string) ($draft['updated_at'] ?? current_time('mysql')));
            $changed = true;
        }
        if ($changed) {
            $this->aysa_save_adverlink_orders($project_id, $orders);
        }
    }

    private function aysa_agent8_offpage_snapshot_for_task($project_id, $task) {
        $project_id = intval($project_id);
        $task = sanitize_key((string) $task);
        return match ($task) {
            'publisher_draft_approve' => $this->aysa_agent8_get_offpage_draft_articles_snapshot($project_id),
            'exchange_request_respond', 'exchange_change_state', 'exchange_simulate_accept', 'exchange_activate_link', 'exchange_refresh_verification' => array(
                'profile' => $this->aysa_agent8_get_offpage_link_exchange_profile_snapshot($project_id),
                'orders' => $this->aysa_agent8_get_offpage_orders_snapshot($project_id),
            ),
            default => array(),
        };
    }

    private function aysa_agent8_offpage_runtime_bundle_for_action($project_id, $action_key) {
        $project_id = intval($project_id);
        $action_key = sanitize_key((string) $action_key);
        $full = $this->aysa_agent8_runtime_bundle($project_id);

        return match ($action_key) {
            'set_offpage_gbp_primary_location',
            'refresh_offpage_gbp_reviews',
            'refresh_offpage_gbp_performance',
            'create_offpage_gbp_post' => $this->aysa_agent8_runtime_bundle_subset($full, array('offpage_gbp_overview')),
            'reply_offpage_gbp_review',
            'upload_offpage_gbp_media',
            'update_offpage_gbp_business_info',
            'update_offpage_gbp_hours',
            'update_offpage_gbp_attributes',
            'update_offpage_gbp_services' => $this->aysa_agent8_runtime_bundle_subset($full, array('offpage_gbp_overview')),
            'show_offpage_gbp_social_calendar',
            'generate_offpage_gbp_social_calendar',
            'refresh_offpage_gbp_social_calendar_status',
            'reject_offpage_gbp_social_post',
            'retry_offpage_gbp_social_post' => $this->aysa_agent8_runtime_bundle_subset($full, array('offpage_gbp_social_calendar', 'offpage_gbp_social_sources', 'offpage_gbp_overview')),
            'run_offpage_backlink_audit',
            'refresh_offpage_backlink_audit_status' => $this->aysa_agent8_runtime_bundle_subset($full, array('offpage_backlink_audit', 'offpage_backlink_strategy')),
            'generate_offpage_backlink_strategy',
            'refresh_offpage_backlink_strategy_status',
            'generate_offpage_adverlink_article',
            'refresh_offpage_adverlink_article_status',
            'publish_offpage_adverlink_article',
            'queue_offpage_publisher_article_order',
            'preview_offpage_link_building_cart',
            'approve_offpage_link_building_cart' => $this->aysa_agent8_runtime_bundle_subset($full, array('offpage_backlink_audit', 'offpage_backlink_strategy', 'offpage_link_exchange_profile', 'offpage_adverlink_orders')),
            'accept_offpage_exchange_request', 'pause_offpage_exchange_request', 'simulate_accept_offpage_exchange_request', 'insert_offpage_exchange_partner_box', 'verify_offpage_exchange_request' => $this->aysa_agent8_runtime_bundle_subset($full, array('offpage_link_exchange_profile', 'offpage_adverlink_orders')),
            'preview_offpage_draft_article',
            'send_offpage_draft_article_approval_email',
            'approve_offpage_draft_article' => $this->aysa_agent8_runtime_bundle_subset($full, array('offpage_draft_articles', 'offpage_adverlink_orders')),
            'approve_link_opportunity',
            'reject_link_opportunity' => $this->aysa_agent8_runtime_bundle_subset($full, array('offpage_link_exchange_profile', 'offpage_adverlink_orders', 'offpage_backlink_audit', 'offpage_draft_articles', 'offpage_published_articles')),
            default => array(),
        };
    }

    private function aysa_agent8_mark_technical_gone_candidate($project_id, array $technical_operation) {
        $project_id = intval($project_id);
        $source = $this->aysa_normalize_redirect_source((string) ($technical_operation['source_url'] ?? ''));
        if ($project_id <= 0 || $source === '') {
            return array('applied' => false, 'reason' => 'gone_target_required');
        }

        $result = $this->aysa_add_htaccess_gone_rule($source);
        if (empty($result['ok'])) {
            return array(
                'applied' => false,
                'reason' => sanitize_key((string) ($result['code'] ?? 'gone_apply_failed')),
                'message' => (string) ($result['message'] ?? 'Gone apply failed.'),
            );
        }

        return array(
            'applied' => empty($result['already_done']),
            'already_done' => !empty($result['already_done']),
            'operation' => 'mark_technical_gone_candidate',
            'summary' => array(
                'source_url' => $source,
                'redirect_removed' => (int) (!empty($result['redirect_removed'])),
                'already_done' => !empty($result['already_done']),
            ),
            'snapshot' => $this->aysa_adjustments_get_technical_errors_snapshot($project_id),
        );
    }

    private function aysa_agent8_ignore_technical_404_candidate($project_id, array $technical_operation) {
        global $wpdb;

        $project_id = intval($project_id);
        $source = $this->aysa_normalize_redirect_source((string) ($technical_operation['source_url'] ?? ''));
        if ($project_id <= 0 || $source === '') {
            return array('applied' => false, 'reason' => 'ignore_target_required');
        }

        $table_name = esc_sql($wpdb->prefix . 'aysa_404_errors');
        $variants = array_values(array_unique(array_filter(array(
            $source,
            untrailingslashit($source),
            trailingslashit($source),
        ))));
        if ($variants === array()) {
            return array('applied' => false, 'reason' => 'ignore_target_required');
        }

        $placeholders = implode(',', array_fill(0, count($variants), '%s'));
        // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Placeholder count is generated from the validated variants array and every value is passed to prepare below.
        $query = $wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT id FROM {$table_name} WHERE is_ignored = 0 AND url IN ({$placeholders})",
            ...$variants
        );
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query: table identifiers and structure are internal, and request-derived values are prepared before execution. Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $ids = $query ? $wpdb->get_col($query) : array();
        $ids = array_values(array_filter(array_map('intval', (array) $ids)));
        if ($ids === array()) {
            return array(
                'applied' => false,
                'already_done' => true,
                'reason' => 'already_ignored',
                'message' => 'This 404 is no longer active or was already ignored.',
                'snapshot' => $this->aysa_adjustments_get_technical_errors_snapshot($project_id),
            );
        }

        $updated = 0;
        foreach ($ids as $error_id) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $result = $wpdb->update(
                $table_name,
                array('is_ignored' => 1),
                array('id' => $error_id),
                array('%d'),
                array('%d')
            );
            if ($result === false) {
                return array('applied' => false, 'reason' => '404_ignore_failed');
            }
            $updated += intval($result);
        }

        return array(
            'applied' => $updated > 0,
            'operation' => 'ignore_technical_404_candidate',
            'summary' => array(
                'source_url' => $source,
                'ignored_rows' => $updated,
            ),
            'snapshot' => $this->aysa_adjustments_get_technical_errors_snapshot($project_id),
        );
    }

    private function aysa_agent8_apply_technical_redirect_batch($project_id, array $technical_operation, $mode) {
        $project_id = intval($project_id);
        $mode = sanitize_key((string) $mode);
        $selection = isset($technical_operation['selection']) && is_array($technical_operation['selection'])
            ? array_values($technical_operation['selection'])
            : array();
        if ($project_id <= 0 || $selection === array()) {
            return array('applied' => false, 'reason' => 'technical_redirect_batch_selection_required');
        }

        $applied = 0;
        $failed = 0;
        $already_done = 0;
        foreach ($selection as $item) {
            if (!is_array($item)) {
                continue;
            }
            $result = match ($mode) {
                'redirect' => $this->aysa_agent8_apply_technical_redirect_candidate($project_id, $item),
                'gone' => $this->aysa_agent8_mark_technical_gone_candidate($project_id, $item),
                'ignore' => $this->aysa_agent8_ignore_technical_404_candidate($project_id, $item),
                default => array('applied' => false, 'reason' => 'unsupported_batch_mode'),
            };
            if (!empty($result['applied'])) {
                $applied++;
            } elseif (!empty($result['already_done'])) {
                $already_done++;
            } else {
                $failed++;
            }
        }

        return array(
            'applied' => $applied > 0,
            'operation' => 'technical_redirect_batch_' . $mode,
            'summary' => array(
                'selected_entity_count' => count($selection),
                'applied_count' => $applied,
                'already_done_count' => $already_done,
                'failed_count' => $failed,
            ),
            'snapshot' => $this->aysa_adjustments_get_technical_errors_snapshot($project_id),
        );
    }

    private function aysa_agent8_email_technical_pagespeed_pdf($project_id, array $technical_operation) {
        $project_id = intval($project_id);
        $recipient = sanitize_email((string) ($technical_operation['recipient_email'] ?? ''));
        $download_url = esc_url_raw((string) ($technical_operation['download_url'] ?? ''));
        $file_name = sanitize_text_field((string) ($technical_operation['file_name'] ?? 'aysa-pagespeed-report.pdf'));
        $summary = isset($technical_operation['summary']) && is_array($technical_operation['summary']) ? $technical_operation['summary'] : array();
        if ($project_id <= 0 || $recipient === '' || !is_email($recipient) || $download_url === '') {
            return array('applied' => false, 'reason' => 'pagespeed_email_payload_required');
        }

        $snapshot = $this->aysa_adjustments_get_technical_pagespeed_snapshot($project_id);
        $subject = 'AYSA PageSpeed AI Report';
        $body_lines = array(
            'The latest PageSpeed AI report is ready.',
            '',
            'Website: ' . sanitize_text_field((string) ($snapshot['url'] ?? home_url('/'))),
            'Overall score: ' . intval($summary['overall_score'] ?? $snapshot['overall_score'] ?? 0),
            'Desktop score: ' . intval($summary['desktop_score'] ?? $snapshot['desktop_score'] ?? 0),
            'Mobile score: ' . intval($summary['mobile_score'] ?? $snapshot['mobile_score'] ?? 0),
            'Verdict: ' . sanitize_text_field((string) ($summary['verdict'] ?? '')),
            '',
            'Download report: ' . $download_url,
        );

        $mail_result = $this->aysa_agent8_send_communication_email(
            $recipient,
            $subject,
            implode("\n", $body_lines),
            array(
                'module' => 'technical_pagespeed',
                'trigger' => 'pagespeed_report_ready',
                'project_id' => $project_id,
                'action_ref' => 'pagespeed_report:' . $project_id,
                'deep_link' => $download_url,
                'reply_approval_supported' => false,
            )
        );
        if (empty($mail_result['ok'])) {
            return array('applied' => false, 'reason' => 'pagespeed_email_send_failed');
        }

        $state_key = $this->aysa_technical_pagespeed_state_key($project_id);
        $state = get_option($state_key, array());
        if (!is_array($state)) {
            $state = array();
        }
        $state['last_report_email'] = array(
            'recipient_email' => $recipient,
            'download_url' => $download_url,
            'file_name' => $file_name,
            'sent_at' => current_time('mysql'),
        );
        update_option($state_key, $state, false);

        return array(
            'applied' => true,
            'operation' => 'email_technical_pagespeed_pdf',
            'summary' => $state['last_report_email'],
            'snapshot' => $this->aysa_adjustments_get_technical_pagespeed_snapshot($project_id),
        );
    }

    private function aysa_agent8_generate_technical_sitemap($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array('applied' => false, 'reason' => 'invalid_project_id');
        }

        $target_url = $this->aysa_technical_resolve_target_url();
        $generation = $this->aysa_technical_generate_own_sitemap($target_url);
        $audit = $this->aysa_technical_build_sitemap_robots_audit($target_url);
        update_option($this->aysa_technical_sitemap_robots_state_key($project_id), array(
            'project_id' => $project_id,
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'data' => $audit,
        ), false);

        return array(
            'applied' => true,
            'operation' => 'generate_aysa_sitemap',
            'summary' => array(
                'entries_count' => intval($generation['entries_count'] ?? 0),
                'mode' => sanitize_text_field((string) ($generation['mode'] ?? 'virtual')),
            ),
            'snapshot' => $this->aysa_adjustments_get_technical_sitemap_robots_snapshot($project_id),
        );
    }

    private function aysa_agent8_save_technical_robots($project_id, array $technical_operation) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array('applied' => false, 'reason' => 'invalid_project_id');
        }
        $target_url = $this->aysa_technical_resolve_target_url();
        $content = isset($technical_operation['content']) && is_string($technical_operation['content']) && trim($technical_operation['content']) !== ''
            ? (string) $technical_operation['content']
            : $this->aysa_technical_robots_default_content($target_url, $this->aysa_technical_preferred_sitemap_url($target_url));
        $saved = $this->aysa_technical_save_robots_content($content);
        if (is_wp_error($saved)) {
            return array('applied' => false, 'reason' => 'robots_save_failed', 'message' => $saved->get_error_message());
        }
        $audit = $this->aysa_technical_build_sitemap_robots_audit($target_url);
        update_option($this->aysa_technical_sitemap_robots_state_key($project_id), array(
            'project_id' => $project_id,
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'data' => $audit,
        ), false);
        return array(
            'applied' => true,
            'operation' => 'save_technical_robots',
            'snapshot' => $this->aysa_adjustments_get_technical_sitemap_robots_snapshot($project_id),
        );
    }

    private function aysa_agent8_restore_technical_robots($project_id) {
        return $this->aysa_agent8_save_technical_robots($project_id, array());
    }

    private function aysa_agent8_enforce_aysa_sitemap_authority($project_id) {
        $project_id = intval($project_id);
        $sitemap = $this->aysa_agent8_generate_technical_sitemap($project_id);
        if (empty($sitemap['applied'])) {
            return $sitemap;
        }
        $robots = $this->aysa_agent8_restore_technical_robots($project_id);
        if (empty($robots['applied'])) {
            return $robots;
        }
        return array(
            'applied' => true,
            'operation' => 'enforce_aysa_sitemap_authority',
            'summary' => array(
                'sitemap_authority' => 'aysa',
            ),
            'snapshot' => $this->aysa_adjustments_get_technical_sitemap_robots_snapshot($project_id),
        );
    }

    private function aysa_agent8_apply_unblock_technical_robots($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array('applied' => false, 'reason' => 'invalid_project_id');
        }

        $target_url = $this->aysa_technical_resolve_target_url();
        $content = $this->aysa_technical_robots_default_content($target_url, $this->aysa_technical_preferred_sitemap_url($target_url));
        $saved = $this->aysa_technical_save_robots_content($content);
        if (is_wp_error($saved)) {
            return array(
                'applied' => false,
                'reason' => 'robots_save_failed',
                'message' => $saved->get_error_message(),
            );
        }

        $audit = $this->aysa_technical_build_sitemap_robots_audit($target_url);
        update_option($this->aysa_technical_sitemap_robots_state_key($project_id), array(
            'project_id' => $project_id,
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'data' => $audit,
        ), false);

        return array(
            'applied' => true,
            'operation' => 'unblock_technical_robots',
            'snapshot' => $this->aysa_adjustments_get_technical_sitemap_robots_snapshot($project_id),
        );
    }

    private function aysa_agent8_build_technical_overview_command($site_audit, $pagespeed, $sitemap_robots, $internal_linking, $technical_errors = array(), $technical_schema = array()) {
        return array(
            'type' => 'open_technical_overview',
            'title' => 'Technical Optimization',
            'intro' => 'Aici vezi starea tehnică agregată, ce se poate rezolva automat și ce rămâne în review.',
            'sections' => array(
                array(
                    'title' => 'Site Audit',
                    'summary' => array(
                        'Status' => sanitize_key((string) ($site_audit['status'] ?? 'idle')),
                        'Issues' => (string) intval($site_audit['count'] ?? 0),
                        'Pages crawled' => (string) intval($site_audit['pages_crawled'] ?? 0),
                    ),
                ),
                array(
                    'title' => 'PageSpeed',
                    'summary' => array(
                        'Status' => sanitize_key((string) ($pagespeed['status'] ?? 'idle')),
                        'Overall score' => (string) intval($pagespeed['overall_score'] ?? 0),
                        'Mobile score' => (string) intval($pagespeed['mobile_score'] ?? 0),
                    ),
                ),
                array(
                    'title' => 'Sitemap + Robots',
                    'summary' => array(
                        'Status' => sanitize_key((string) ($sitemap_robots['status'] ?? 'idle')),
                        'Errors' => (string) intval($sitemap_robots['errors'] ?? 0),
                        'Warnings' => (string) intval($sitemap_robots['warnings'] ?? 0),
                        'Primary sitemap' => sanitize_text_field((string) ($sitemap_robots['primary_sitemap'] ?? '-')),
                    ),
                ),
                array(
                    'title' => 'Errors Hub',
                    'summary' => array(
                        'Redirect ready' => (string) intval($technical_errors['summary']['redirect_ready'] ?? 0),
                        'Gone candidates' => (string) intval($technical_errors['summary']['gone_candidates'] ?? 0),
                        'Review required' => (string) intval($technical_errors['summary']['review_required'] ?? 0),
                    ),
                ),
                array(
                    'title' => 'Schema Markup',
                    'summary' => array(
                        'Status' => sanitize_key((string) ($technical_schema['status'] ?? 'idle')),
                        'Pages' => (string) intval($technical_schema['summary']['total_pages'] ?? 0),
                        'Issues' => (string) intval($technical_schema['summary']['issue_count'] ?? 0),
                    ),
                ),
                array(
                    'title' => 'Internal Linking',
                    'summary' => array(
                        'Status' => sanitize_key((string) ($internal_linking['status'] ?? 'idle')),
                        'Keywords linked' => (string) intval($internal_linking['count'] ?? 0),
                        'Preview links to add' => (string) intval($internal_linking['preview_links_to_add'] ?? 0),
                    ),
                ),
            ),
        );
    }

    private function aysa_agent8_build_technical_site_audit_command($snapshot) {
        return array(
            'type' => 'open_technical_site_audit_review',
            'title' => 'Technical Site Audit',
            'intro' => 'Aici vezi auditul tehnic salvat: overview, problemele principale, paginile crawl-ate și statisticile importante.',
            'snapshot' => array(
                'overview_rows' => isset($snapshot['overview_rows']) && is_array($snapshot['overview_rows']) ? $snapshot['overview_rows'] : array(),
                'conclusion' => isset($snapshot['conclusion']) && is_array($snapshot['conclusion']) ? $snapshot['conclusion'] : array(),
                'rows' => array_slice(isset($snapshot['rows']) && is_array($snapshot['rows']) ? $snapshot['rows'] : array(), 0, 20),
                'pages' => array_slice(isset($snapshot['pages']) && is_array($snapshot['pages']) ? $snapshot['pages'] : array(), 0, 20),
                'exports' => isset($snapshot['exports']) && is_array($snapshot['exports']) ? $snapshot['exports'] : array(),
                'status' => sanitize_key((string) ($snapshot['status'] ?? 'idle')),
                'updated_at' => sanitize_text_field((string) ($snapshot['updated_at'] ?? '')),
                'version_number' => intval($snapshot['version_number'] ?? 0),
                'history_count' => intval($snapshot['history_count'] ?? 0),
                'mode' => sanitize_text_field((string) ($snapshot['mode'] ?? 'baseline_full')),
            ),
        );
    }

    private function aysa_agent8_build_technical_pagespeed_command($snapshot) {
        return array(
            'type' => 'open_technical_pagespeed_review',
            'title' => 'PageSpeed',
            'intro' => 'Aici vezi scorurile curente de viteză și cele mai importante oportunități de optimizare.',
            'snapshot' => array(
                'overview_rows' => isset($snapshot['overview_rows']) && is_array($snapshot['overview_rows']) ? $snapshot['overview_rows'] : array(),
                'metrics_rows' => isset($snapshot['metrics_rows']) && is_array($snapshot['metrics_rows']) ? $snapshot['metrics_rows'] : array(),
                'rows' => array_slice(isset($snapshot['rows']) && is_array($snapshot['rows']) ? $snapshot['rows'] : array(), 0, 12),
                'priority_actions' => isset($snapshot['priority_actions']) && is_array($snapshot['priority_actions']) ? $snapshot['priority_actions'] : array(),
                'status' => sanitize_key((string) ($snapshot['status'] ?? 'idle')),
                'updated_at' => sanitize_text_field((string) ($snapshot['updated_at'] ?? '')),
            ),
        );
    }

    private function aysa_agent8_build_technical_sitemap_robots_command($snapshot) {
        return array(
            'type' => 'open_technical_sitemap_robots_review',
            'title' => 'Sitemap + Robots',
            'intro' => 'Aici vezi verificările pentru sitemap și robots.txt, plus fișierul robots curent.',
            'snapshot' => array(
                'overview_rows' => isset($snapshot['overview_rows']) && is_array($snapshot['overview_rows']) ? $snapshot['overview_rows'] : array(),
                'rows' => array_slice(isset($snapshot['rows']) && is_array($snapshot['rows']) ? $snapshot['rows'] : array(), 0, 20),
                'sitemaps' => isset($snapshot['sitemaps']) && is_array($snapshot['sitemaps']) ? $snapshot['sitemaps'] : array(),
                'robots_rows' => isset($snapshot['robots_rows']) && is_array($snapshot['robots_rows']) ? $snapshot['robots_rows'] : array(),
                'status' => sanitize_key((string) ($snapshot['status'] ?? 'idle')),
                'updated_at' => sanitize_text_field((string) ($snapshot['updated_at'] ?? '')),
            ),
        );
    }

    private function aysa_agent8_build_technical_internal_linking_command($snapshot) {
        return array(
            'type' => 'open_technical_internal_linking_review',
            'title' => 'Internal Linking',
            'intro' => 'Aici vezi rezumatul actual pentru internal linking și preview-ul ultimului pass pregătit.',
            'snapshot' => array(
                'overview_rows' => isset($snapshot['overview_rows']) && is_array($snapshot['overview_rows']) ? $snapshot['overview_rows'] : array(),
                'rows' => array_slice(isset($snapshot['rows']) && is_array($snapshot['rows']) ? $snapshot['rows'] : array(), 0, 20),
                'preview_rows' => array_slice(isset($snapshot['preview_rows']) && is_array($snapshot['preview_rows']) ? $snapshot['preview_rows'] : array(), 0, 20),
                'preview_summary' => sanitize_text_field((string) ($snapshot['preview_summary'] ?? '')),
                'status' => sanitize_key((string) ($snapshot['status'] ?? 'idle')),
            ),
        );
    }

    private function aysa_store_verified_access_key($validation, $access_key, $site_host) {
        update_option($this->aysa_test_access_key_option_name(), (string) $access_key, false);

        if (!empty($validation['project_id'])) {
            update_option('aysa_project_id', intval($validation['project_id']), false);
        }
        if (!empty($validation['website_id'])) {
            update_option('aysa_website_id', intval($validation['website_id']), false);
        }
        $this->aysa_store_projection_sync_secret_from_validation($validation);

        $auth_bootstrap = null;
        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }
        if (class_exists('Aysa_Auth_Manager')) {
            $auth_manager = Aysa_Auth_Manager::get_instance();
            $auth_bootstrap = $auth_manager->verify_and_store_secret_key((string) $access_key, (string) $site_host);

            if (is_array($auth_bootstrap) && !empty($auth_bootstrap['success'])) {
                update_option('aysa_user_secret_key', (string) $access_key, false);
                update_option('aysa_user_id', intval($auth_bootstrap['aysa_user_id'] ?? 0), false);
                update_option('aysa_auth_domain', $site_host !== '' ? $site_host : wp_parse_url(home_url(), PHP_URL_HOST), false);
            }
        }

        if (!empty($validation['plan_code'])) {
            update_option('aysa_wallet_default_plan', sanitize_key((string) $validation['plan_code']), false);
            if (class_exists('Aysa_Wallet_Manager')) {
                $project_id = intval(get_option('aysa_project_id', 0));
                if ($project_id > 0) {
                    Aysa_Wallet_Manager::get_instance()->apply_plan_to_project(
                        $project_id,
                        sanitize_key((string) $validation['plan_code']),
                        false
                    );
                }
            }
        }
    }

    private function aysa_clear_activation_binding($wp_user_id = 0) {
        delete_option($this->aysa_test_access_key_option_name());
        delete_option('aysa_active_access_key');
        delete_option('aysa_user_secret_key');
        delete_option('aysa_user_id');
        delete_option('aysa_auth_domain');
        delete_option('aysa_project_id');
        delete_option('aysa_website_id');
        delete_option('aysa_wallet_app_internal_secret');
        delete_option('aysa_wallet_app_internal_secret_version');
        delete_option('aysa_wallet_app_internal_secret_scope');

        global $wpdb;
        $domain = (string) wp_parse_url(home_url(), PHP_URL_HOST);
        $domain = strtolower(preg_replace('/^www\./', '', trim($domain)));
        if ($domain !== '') {
            $table_name = esc_sql($wpdb->prefix . 'aysa_user_auth');
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->delete(
                $table_name,
                array('domain' => $domain),
                array('%s')
            );
        }

        if (!class_exists('Aysa_Auth_Manager')) {
            $auth_manager_file = AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
            if (file_exists($auth_manager_file)) {
                require_once $auth_manager_file;
            }
        }

        if (class_exists('Aysa_Auth_Manager')) {
            $auth_manager = Aysa_Auth_Manager::get_instance();
            if (is_object($auth_manager) && method_exists($auth_manager, 'logout')) {
                $auth_manager->logout($wp_user_id > 0 ? intval($wp_user_id) : get_current_user_id());
            }
        }
    }

    public function maybe_cleanup_retired_agent7_runtime() {
        if (!is_admin() || wp_doing_ajax()) {
            return;
        }
        if (!$this->aysa_current_user_can_workspace()) {
            return;
        }
        if (get_option('aysa_agent7_retired_cleanup_done', '0') === '1') {
            return;
        }

        global $wpdb;
        if (isset($wpdb->usermeta)) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->query(
                $wpdb->prepare(
                    "DELETE FROM {$wpdb->usermeta} WHERE meta_key LIKE %s",
                    'aysa_agent7_local_%'
                )
            );
        }
        if (isset($wpdb->options)) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->query(
                $wpdb->prepare(
                    "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
                    'aysa_agent7_%'
                )
            );
        }

        $project_id = intval(get_option('aysa_project_id', 0));
        $site_host = (string) wp_parse_url(home_url('/'), PHP_URL_HOST);
        if ($project_id > 0) {
            $this->aysa_broker_request('POST', '/v1/conversation/apprentice/reset', array(
                'project_id' => $project_id,
                'canonical_project_id' => $project_id,
                'wp_project_id' => $project_id,
                'site_host' => $site_host,
                'channels' => array('agent7', 'agent7_v2'),
            ), 20);
        }

        update_option('aysa_agent7_retired_cleanup_done', '1', false);
    }

    public function handle_insight_banner_dismiss() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }
        if (!$this->check_authentication(false)) {
            wp_send_json_error(array('message' => 'Authentication required.'));
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0);
        $banner_id = isset($_POST['banner_id']) ? sanitize_key((string) wp_unslash($_POST['banner_id'])) : '';
        $dismiss_version = isset($_POST['dismiss_version']) ? sanitize_text_field((string) wp_unslash($_POST['dismiss_version'])) : 'v1';

        if ($project_id <= 0 || $banner_id === '') {
            wp_send_json_error(array('message' => 'Invalid dismiss payload.'));
            return;
        }

        $result = $this->aysa_broker_request('POST', '/v1/conversation/insight-banner/dismiss', array(
            'project_id' => $project_id,
            'banner_id' => $banner_id,
            'dismiss_version' => $dismiss_version !== '' ? $dismiss_version : 'v1',
        ), 20);

        if (is_wp_error($result)) {
            wp_send_json_error(array('message' => $result->get_error_message()));
            return;
        }
        if (!is_array($result) || empty($result['ok'])) {
            wp_send_json_error(array(
                'message' => isset($result['error']) ? (string) $result['error'] : 'Insight banner dismiss failed.',
            ));
            return;
        }

        wp_send_json_success($result);
    }

    public function handle_activation_reset_test() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }

        if (!$this->aysa_qa_controls_enabled()) {
            wp_send_json_error(array('message' => 'QA controls are unavailable.'), 404);
            return;
        }

        delete_option($this->aysa_test_access_key_option_name());
        delete_option('aysa_active_access_key');
        delete_option('aysa_user_secret_key');
        delete_option('aysa_user_id');
        delete_option('aysa_auth_domain');

        global $wpdb;
        $domain = (string) wp_parse_url(home_url(), PHP_URL_HOST);
        $domain = strtolower(preg_replace('/^www\./', '', trim($domain)));
        if ($domain !== '') {
            $table_name = esc_sql($wpdb->prefix . 'aysa_user_auth');
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->delete(
                $table_name,
                array('domain' => $domain),
                array('%s')
            );
        }

        wp_send_json_success(array(
            'message' => 'Activation test state cleared.',
            'host' => $domain,
        ));
    }
    
    /**
     * Sanitize wizard data before saving
     */
    private function sanitize_wizard_data($data) {
        $sanitized = [];
        
        foreach ($data as $key => $value) {
            $clean_key = sanitize_key((string) $key);
            if (is_array($value)) {
                $sanitized[$clean_key] = array_map(static function ($item) {
                    return sanitize_text_field((string) $item);
                }, $value);
            } else {
                $sanitized[$clean_key] = sanitize_text_field((string) $value);
            }
        }
        
        return $sanitized;
    }

    /**
     * Register all the hooks related to the admin area functionality
     * of the plugin.
     */
    private function register_hooks() {
        // Add menu items
        add_action('admin_menu', array($this, 'add_plugin_admin_menu'));
        
        // Add AJAX handlers
        add_action('wp_ajax_aysa_save_wizard_data', array($this, 'handle_save_wizard_data'));
        add_action('wp_ajax_aysa_get_wizard_data', array($this, 'handle_get_wizard_data'));
        add_action('wp_ajax_aysa_save_project_id', array($this, 'save_project_id'));
        
        // Register stylesheets and scripts
        add_action('admin_enqueue_scripts', array($this, 'enqueue_styles'));
        add_action('admin_enqueue_scripts', array($this, 'enqueue_scripts'));
        add_action('admin_enqueue_scripts', array($this, 'enqueue_credit_analytics_scripts'));
        

    }

    private function get_app_aysa_base_url() {
        $base = trim((string) get_option('aysa_wallet_payment_base_url', 'https://app.aysa.ai/pay'));
        if ($base === '') {
            $base = 'https://app.aysa.ai/pay';
        }
        $base = preg_replace('#/pay/?$#', '', $base);
        if (!is_string($base) || trim($base) === '') {
            return 'https://app.aysa.ai';
        }
        return rtrim($base, '/');
    }

    private function get_current_plugin_domain() {
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $domain = isset($_SERVER['HTTP_HOST']) ? strtolower(trim((string) wp_unslash($_SERVER['HTTP_HOST']))) : '';
        if (strpos($domain, 'www.') === 0) {
            $domain = substr($domain, 4);
        }
        return $domain;
    }

    private function get_plugin_notifications_secret_key() {
        if (class_exists('Aysa_Auth_Manager')) {
            try {
                $auth = Aysa_Auth_Manager::get_instance();
                $status = $auth->is_authenticated();
                $token = is_array($status) ? trim((string) ($status['secret_key'] ?? '')) : '';
                if ($token !== '') {
                    return $token;
                }
            } catch (Exception $e) {
                // Fall through to the same site-level token sources used by wallet sync.
            }
        }

        $token = trim((string) get_option('aysa_user_secret_key', ''));
        if ($token !== '') {
            return $token;
        }

        $authData = get_option('aysa_activator_auth_data', array());
        if (is_array($authData)) {
            $token = trim((string) ($authData['secret_key'] ?? ''));
            if ($token !== '') {
                return $token;
            }
        }

        return '';
    }

    private function make_app_aysa_notifications_request($path) {
        $secretKey = $this->get_plugin_notifications_secret_key();
        $domain = $this->get_current_plugin_domain();

        if ($secretKey === '' || $domain === '') {
            return array(
                'ok' => false,
                'message' => 'Authentication required.',
            );
        }

        $url = $this->get_app_aysa_base_url() . '/' . ltrim((string) $path, '/');
        $response = wp_remote_get($url, array(
            'timeout' => 20,
            'headers' => array(
                'Accept' => 'application/json',
                'Authorization' => 'Bearer ' . $secretKey,
                'X-AYSA-Plugin-Domain' => $domain,
            ),
        ));

        if (is_wp_error($response)) {
            return array(
                'ok' => false,
                'message' => $response->get_error_message(),
            );
        }

        $code = intval(wp_remote_retrieve_response_code($response));
        $body = json_decode((string) wp_remote_retrieve_body($response), true);

        if ($code < 200 || $code >= 300 || !is_array($body)) {
            return array(
                'ok' => false,
                'message' => 'Notification service unavailable.',
            );
        }

        return $body;
    }

    public function handle_notifications_fetch() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_notifications_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }

        if (!$this->check_authentication(false)) {
            wp_send_json_error(array('message' => 'Authentication required.'));
            return;
        }

        $this->clear_cached_notification_notice();
        $result = $this->make_app_aysa_notifications_request('/bridge/plugin-notifications');
        if (empty($result['ok'])) {
            wp_send_json_error(array('message' => (string) ($result['message'] ?? 'Failed to load notifications.')));
            return;
        }

        wp_send_json_success($result);
    }

    public function handle_notifications_mark_read() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_notifications_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }

        if (!$this->check_authentication(false)) {
            wp_send_json_error(array('message' => 'Authentication required.'));
            return;
        }

        $notificationId = intval(wp_unslash($_POST['notification_id'] ?? 0));
        if ($notificationId <= 0) {
            wp_send_json_error(array('message' => 'Invalid notification.'));
            return;
        }

        $result = $this->make_app_aysa_notifications_request('/bridge/plugin-notifications/' . $notificationId . '/read');
        if (empty($result['ok'])) {
            wp_send_json_error(array('message' => (string) ($result['message'] ?? 'Failed to update notification.')));
            return;
        }

        $this->clear_cached_notification_notice();
        wp_send_json_success($result);
    }

    public function handle_notifications_mark_all_read() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_notifications_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }

        if (!$this->check_authentication(false)) {
            wp_send_json_error(array('message' => 'Authentication required.'));
            return;
        }

        $result = $this->make_app_aysa_notifications_request('/bridge/plugin-notifications/read-all');
        if (empty($result['ok'])) {
            wp_send_json_error(array('message' => (string) ($result['message'] ?? 'Failed to update notifications.')));
            return;
        }

        $this->clear_cached_notification_notice();
        wp_send_json_success($result);
    }

    /**
     * Save project ID to WordPress options
     */
    public function save_project_id() {
        // Check for nonce security
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_save_project_id_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }

        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }
        
        // Get project ID and name from POST data
        $project_id = $this->aysa_resolve_installed_project_id(isset($_POST['project_id']) ? absint((string) wp_unslash($_POST['project_id'])) : 0);
        $project_name = isset($_POST['project_name']) ? sanitize_text_field((string) wp_unslash($_POST['project_name'])) : '';

        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Project does not match this WordPress installation.'));
            return;
        }
        
        // Save to WordPress options
        update_option('aysa_project_id', $project_id);
        update_option('aysa_project_name', $project_name);
        
        // Send success response
        wp_send_json_success(array('message' => 'Project ID saved successfully.'));
    }

    /**
     * Get saved wizard data for a specific step.
     */
    public function handle_get_wizard_data() {
        $nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (empty($nonce) || (!wp_verify_nonce($nonce, 'aysa_wizard_nonce') && !wp_verify_nonce($nonce, 'aysa_nonce'))) {
            wp_send_json_error(array('message' => 'Security check failed'));
            return;
        }

        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions'));
            return;
        }

        if (!$this->check_authentication(false)) {
            wp_send_json_error(array('message' => 'Authentication required'));
            return;
        }

        $step = isset($_POST['step']) ? sanitize_text_field((string) wp_unslash($_POST['step'])) : '';
        if ($step === '') {
            wp_send_json_error(array('message' => 'Step is required'));
            return;
        }

        $auth_manager = Aysa_Auth_Manager::get_instance();
        $auth_status = $auth_manager->is_authenticated();
        $user_id = isset($auth_status['aysa_user_id']) ? intval($auth_status['aysa_user_id']) : 0;
        if ($user_id <= 0) {
            wp_send_json_error(array('message' => 'Invalid authentication context'));
            return;
        }

        $option_name = 'aysa_wizard_' . $step . '_' . $user_id;
        $saved_data = get_option($option_name, array());
        if (!is_array($saved_data)) {
            $saved_data = array();
        }

        wp_send_json_success(array('step' => $step, 'data' => $saved_data));
    }

    /**
     * AJAX handler to fetch WooCommerce categories
     */
    public function get_woocommerce_categories() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error('Security check failed');
            return;
        }

        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        // Check if WooCommerce is active
        if (!class_exists('WooCommerce')) {
            wp_send_json_error('WooCommerce is not active');
            return;
        }
        
        // Get all product categories
        $categories = get_terms(array(
            'taxonomy' => 'product_cat',
            'hide_empty' => false,
        ));
        
        // Format the categories for the response
        $formatted_categories = array();
        
        if (!is_wp_error($categories) && !empty($categories)) {
            foreach ($categories as $category) {
                $formatted_categories[] = array(
                    'id' => $category->term_id,
                    'name' => $category->name,
                    'slug' => $category->slug,
                    'count' => $category->count
                );
            }
        }
        
        wp_send_json_success($formatted_categories);
    }

    /**
     * Handle adding .htaccess redirect
     */
    private function aysa_get_redirects_table_name() {
        global $wpdb;

        return $wpdb->prefix . 'aysa_redirects';
    }

    private function aysa_redirects_table_exists() {
        global $wpdb;

        $table_name = esc_sql($this->aysa_get_redirects_table_name());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $found = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table_name));

        return is_string($found) && $found === $table_name;
    }

    private function aysa_normalize_redirect_path($value) {
        $value = trim((string) $value);
        if ($value === '') {
            return '';
        }

        if (preg_match('#^https?://#i', $value)) {
            $parsed = wp_parse_url($value, PHP_URL_PATH);
            $value = is_string($parsed) ? $parsed : '/';
        }

        if ($value === '') {
            $value = '/';
        }

        if (strpos($value, '/') !== 0) {
            $value = '/' . $value;
        }

        return $value;
    }

    private function aysa_parse_htaccess_redirect_rules() {
        $htaccess_file = ABSPATH . '.htaccess';
        if (!file_exists($htaccess_file) || !is_readable($htaccess_file)) {
            return array();
        }

        $content = file_get_contents($htaccess_file);
        if (!is_string($content) || $content === '') {
            return array();
        }

        $section_start = '# BEGIN Aysa Redirects';
        $section_end = '# END Aysa Redirects';
        if (strpos($content, $section_start) === false || strpos($content, $section_end) === false) {
            return array();
        }

        $pattern = '/' . preg_quote($section_start, '/') . '(.*?)' . preg_quote($section_end, '/') . '/s';
        if (!preg_match($pattern, $content, $matches)) {
            return array();
        }

        $rows = array();
        $lines = preg_split('/\r\n|\r|\n/', (string) ($matches[1] ?? ''));
        foreach ($lines as $line) {
            $line = trim((string) $line);
            if ($line === '' || stripos($line, 'RedirectMatch 301 ') !== 0) {
                continue;
            }

            if (!preg_match('/^RedirectMatch\s+301\s+\^(.+?)\$\s+(.+)$/i', $line, $parts)) {
                continue;
            }

            $source = stripslashes((string) $parts[1]);
            $source = preg_replace('/\\\\([\\/])/', '$1', $source);
            $source = $this->aysa_normalize_redirect_path($source);
            $target = trim((string) $parts[2]);
            if ($source === '' || $target === '') {
                continue;
            }

            $rows[$source] = array(
                'source_url' => $source,
                'target_url' => $target,
                'redirect_type' => '301',
                'is_active' => 1,
                'hit_count' => 0,
                'created_at' => '',
                'scope' => '.htaccess',
                'local_id' => null,
            );
        }

        return $rows;
    }

    private function aysa_get_local_redirect_rows() {
        global $wpdb;

        $rows = $this->aysa_parse_htaccess_redirect_rules();
        if (!$this->aysa_redirects_table_exists()) {
            return array_values($rows);
        }

        $table_name = esc_sql($this->aysa_get_redirects_table_name());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $db_rows = $wpdb->get_results("SELECT id, source_url, target_url, redirect_type, is_active, hit_count, created_at FROM {$table_name} ORDER BY created_at DESC, id DESC");
        if (!is_array($db_rows)) {
            return array_values($rows);
        }

        foreach ($db_rows as $db_row) {
            $source = $this->aysa_normalize_redirect_path((string) ($db_row->source_url ?? ''));
            if ($source === '') {
                continue;
            }

            $target = (string) ($db_row->target_url ?? '');
            if ($target === '') {
                continue;
            }

            if (!isset($rows[$source])) {
                $rows[$source] = array(
                    'source_url' => $source,
                    'target_url' => $target,
                    'redirect_type' => (string) ($db_row->redirect_type ?? '301'),
                    'is_active' => intval($db_row->is_active ?? 1),
                    'hit_count' => intval($db_row->hit_count ?? 0),
                    'created_at' => (string) ($db_row->created_at ?? ''),
                    'scope' => 'database',
                    'local_id' => intval($db_row->id ?? 0),
                );
                continue;
            }

            $rows[$source]['local_id'] = intval($db_row->id ?? 0);
            $rows[$source]['hit_count'] = intval($db_row->hit_count ?? 0);
            $rows[$source]['created_at'] = (string) ($db_row->created_at ?? '');
            $rows[$source]['redirect_type'] = (string) ($db_row->redirect_type ?? $rows[$source]['redirect_type']);
            $rows[$source]['is_active'] = intval($db_row->is_active ?? $rows[$source]['is_active']);
            if ($rows[$source]['scope'] !== '.htaccess') {
                $rows[$source]['scope'] = 'database';
            } else {
                $rows[$source]['scope'] = 'database + .htaccess';
            }
        }

        $visible_rows = array();
        foreach ($rows as $row) {
            if ($this->aysa_should_hide_redirect_from_ui($row)) {
                continue;
            }
            $visible_rows[] = $row;
        }

        return array_values($visible_rows);
    }

    /**
     * @param array<string,mixed> $row
     */
    private function aysa_should_hide_redirect_from_ui(array $row): bool {
        $needles = array(
            '/wp-admin/',
            '/wp-login.php',
            '/admin-ajax.php',
            '/xmlrpc.php',
            '/api/',
            'api/aysapluginuser/',
            'api/seo/',
            'api/redirect',
            'api/goneurl',
            'rest_route=',
            'wc-ajax=',
        );

        $haystacks = array(
            strtolower((string) ($row['source_url'] ?? '')),
            strtolower((string) ($row['target_url'] ?? '')),
        );

        foreach ($haystacks as $haystack) {
            if ($haystack === '') {
                continue;
            }
            foreach ($needles as $needle) {
                if (strpos($haystack, $needle) !== false) {
                    return true;
                }
            }
        }

        return false;
    }

    public function handle_get_local_redirects() {
        if (!check_ajax_referer('aysa_nonce', 'nonce', false)) {
            wp_send_json_error('Security check failed.');
            return;
        }

        if (!current_user_can('manage_options')) {
            wp_send_json_error('Permission denied.');
            return;
        }

        wp_send_json_success(array(
            'redirects' => $this->aysa_get_local_redirect_rows(),
        ));
    }

    public function handle_delete_local_redirect() {
        global $wpdb;

        if (!check_ajax_referer('aysa_nonce', 'nonce', false)) {
            wp_send_json_error('Security check failed.');
            return;
        }

        if (!current_user_can('manage_options')) {
            wp_send_json_error('Permission denied.');
            return;
        }

        $source = isset($_POST['source']) ? sanitize_text_field((string) wp_unslash($_POST['source'])) : '';
        $source = $this->aysa_normalize_redirect_path($source);
        $local_id = isset($_POST['local_id']) ? intval(wp_unslash($_POST['local_id'])) : 0;
        if ($source === '' && $local_id <= 0) {
            wp_send_json_error('Redirect source is missing.');
            return;
        }

        if ($source !== '') {
            $result = $this->aysa_remove_htaccess_redirect_rule($source);
            if (empty($result['ok'])) {
                wp_send_json_error((string) ($result['message'] ?? 'Failed to remove redirect from .htaccess.'));
                return;
            }
        }

        if ($this->aysa_redirects_table_exists()) {
            $table_name = esc_sql($this->aysa_get_redirects_table_name());
            if ($local_id > 0) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $wpdb->delete($table_name, array('id' => $local_id), array('%d'));
            } elseif ($source !== '') {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $wpdb->delete($table_name, array('source_url' => $source), array('%s'));
            }
        }

        wp_send_json_success(array(
            'message' => 'Redirect removed successfully.',
        ));
    }

    private function aysa_remove_htaccess_redirect_rule($source) {
        $source = $this->aysa_normalize_redirect_path($source);
        if ($source === '') {
            return array(
                'ok' => false,
                'message' => 'Source URL is missing.',
            );
        }

        $htaccess_file = ABSPATH . '.htaccess';
        if (!file_exists($htaccess_file)) {
            return array(
                'ok' => false,
                'message' => 'The .htaccess file does not exist in the WordPress root directory.',
            );
        }
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- Admin-authorized WordPress-root file operations retain a fail-closed writability preflight before the guarded write.
        if (!is_writable($htaccess_file)) {
            return array(
                'ok' => false,
                'message' => 'The .htaccess file is not writable. Please check file permissions.',
            );
        }

        $htaccess_content = file_get_contents($htaccess_file);
        if (!is_string($htaccess_content)) {
            return array(
                'ok' => false,
                'message' => 'Could not read the .htaccess file.',
            );
        }

        $section_start = '# BEGIN Aysa Redirects';
        $section_end = '# END Aysa Redirects';
        $redirect_pattern = 'RedirectMatch 301 \^' . preg_quote(preg_quote($source, '/'), '/') . '\$';

        if (strpos($htaccess_content, $section_start) === false) {
            return array(
                'ok' => false,
                'message' => 'Redirect rule not found in .htaccess.',
            );
        }

        $pattern = '/' . preg_quote($section_start, '/') . '(.*?)' . preg_quote($section_end, '/') . '/s';
        if (!preg_match($pattern, $htaccess_content, $matches)) {
            return array(
                'ok' => false,
                'message' => 'Redirect rule not found in .htaccess.',
            );
        }

        $existing_content = (string) ($matches[1] ?? '');
        $modified_content = preg_replace('/[\r\n]*' . $redirect_pattern . '.*[\r\n]*/i', "\n", $existing_content);
        if ($modified_content === null || $modified_content === $existing_content) {
            return array(
                'ok' => false,
                'message' => 'Redirect rule not found in .htaccess.',
            );
        }

        $new_content = $section_start . $modified_content . $section_end;
        $htaccess_content = preg_replace($pattern, $new_content, $htaccess_content);
        // phpcs:ignore PluginCheck.CodeAnalysis.WriteFile.ABSPATHDetected -- Admin-authorized redirect operation intentionally updates site-root .htaccess after nonce, capability, path and writability checks.
        if (!is_string($htaccess_content) || file_put_contents($htaccess_file, $htaccess_content) === false) {
            return array(
                'ok' => false,
                'message' => 'Failed to write to .htaccess file.',
            );
        }

        return array(
            'ok' => true,
            'message' => 'Redirect removed successfully from .htaccess file.',
        );
    }

    public function handle_add_htaccess_redirect() {
        // Check for nonce security
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error('Security check failed.');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions.');
            return;
        }
        
        // Get redirect data from POST
        $source = isset($_POST['source']) ? sanitize_text_field((string) wp_unslash($_POST['source'])) : '';
        $destination = isset($_POST['destination']) ? sanitize_text_field((string) wp_unslash($_POST['destination'])) : '';

        $result = $this->aysa_add_htaccess_redirect_rule($source, $destination);
        if (empty($result['ok'])) {
            wp_send_json_error((string) ($result['message'] ?? 'Failed to add redirect.'));
            return;
        }

        $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
        if ($project_id > 0) {
            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_add_htaccess_redirect',
                'add_htaccess_redirect',
                array(
                    'module' => 'technical_optimization',
                    'source' => $source,
                    'destination' => $destination,
                ),
                12,
                '/v1/technical/redirects/htaccess/add'
            );
        }

        wp_send_json_success(array(
            'message' => (string) ($result['message'] ?? 'Redirect added successfully to .htaccess file.'),
            'redirect_rule' => (string) ($result['redirect_rule'] ?? '')
        ));
    }

    /**
     * Handle adding .htaccess gone URL
     */
    private function aysa_add_htaccess_gone_rule($gone_url) {
        $gone_url = sanitize_text_field((string) $gone_url);
        if ($gone_url === '') {
            return array(
                'ok' => false,
                'message' => 'Gone URL is missing.',
            );
        }

        if (strpos($gone_url, '/') !== 0) {
            $gone_url = '/' . $gone_url;
        }

        $htaccess_file = ABSPATH . '.htaccess';
        if (!file_exists($htaccess_file)) {
            return array(
                'ok' => false,
                'message' => 'The .htaccess file does not exist in the WordPress root directory.',
            );
        }

        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- Admin-authorized WordPress-root file operations retain a fail-closed writability preflight before the guarded write.
        if (!is_writable($htaccess_file)) {
            return array(
                'ok' => false,
                'message' => 'The .htaccess file is not writable. Please check file permissions.',
            );
        }

        $htaccess_content = file_get_contents($htaccess_file);
        if (!is_string($htaccess_content)) {
            return array(
                'ok' => false,
                'message' => 'Could not read the .htaccess file.',
            );
        }

        $section_start = '# BEGIN Aysa Redirects';
        $section_end = '# END Aysa Redirects';

        $gone_rule = 'RedirectMatch 410 ^' . preg_quote($gone_url, '/') . '$';
        $redirect_exists = false;

        if (strpos($htaccess_content, $section_start) !== false) {
            $pattern = '/' . preg_quote($section_start, '/') . '(.*?)' . preg_quote($section_end, '/') . '/s';
            if (preg_match($pattern, $htaccess_content, $matches)) {
                $existing_content = (string) ($matches[1] ?? '');

                if (strpos($existing_content, $gone_rule) !== false) {
                    return array(
                        'ok' => true,
                        'already_done' => true,
                        'message' => 'This gone rule already exists in .htaccess.',
                        'code' => 'gone_rule_exists',
                    );
                }

                $redirect_pattern = 'RedirectMatch 301 \^' . preg_quote(preg_quote($gone_url, '/'), '/') . '\$';
                $redirect_exists = preg_match('/' . $redirect_pattern . '/i', $existing_content) === 1;
                $cleaned_content = preg_replace('/[\r\n]*' . $redirect_pattern . '.*[\r\n]*/i', "\n", $existing_content);
                $cleaned_content = is_string($cleaned_content) ? $cleaned_content : $existing_content;

                $new_content = $section_start . "\n" . $cleaned_content . $gone_rule . "\n" . $section_end;
                $htaccess_content = preg_replace($pattern, $new_content, $htaccess_content);
            }
        } else {
            $new_section = $section_start . "\n" . $gone_rule . "\n" . $section_end . "\n\n";
            $htaccess_content = $new_section . $htaccess_content;
        }

        // phpcs:ignore PluginCheck.CodeAnalysis.WriteFile.ABSPATHDetected -- Admin-authorized redirect operation intentionally updates site-root .htaccess after nonce, capability, path and writability checks.
        if (!is_string($htaccess_content) || file_put_contents($htaccess_file, $htaccess_content) === false) {
            return array(
                'ok' => false,
                'message' => 'Failed to write to .htaccess file.',
                'code' => 'htaccess_write_failed',
            );
        }

        return array(
            'ok' => true,
            'message' => 'Gone rule added successfully to .htaccess file.',
            'gone_rule' => $gone_rule,
            'redirect_removed' => $redirect_exists,
            'gone_url' => $gone_url,
        );
    }

    /**
     * Remove the matching persisted redirect after a local 410 rule replaces it.
     * The authenticated requester and installed project binding remain the
     * authority; no browser project or unauthenticated global inventory is used.
     */
    private function aysa_sync_removed_redirect_for_gone($gone_url) {
        $project_id = intval(get_option('aysa_project_id', 0));
        if ($project_id <= 0) {
            return ' Redirect database sync was skipped because the site project is unavailable.';
        }
        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }

        $auth_manager = Aysa_Auth_Manager::get_instance();
        $raw = $auth_manager->make_authenticated_request(
            'api/Redirect?projectId=' . $project_id,
            'GET'
        );
        if ($raw === false) {
            return ' The redirect database could not be reached for synchronization.';
        }

        $redirects = $this->normalize_aysa_collection_response($raw);
        $site_url = site_url();
        $full_url = strpos((string) $gone_url, 'http') === 0
            ? (string) $gone_url
            : rtrim((string) $site_url, '/') . '/' . ltrim((string) $gone_url, '/');
        foreach ($redirects as $redirect) {
            if (!is_array($redirect)) {
                continue;
            }
            $source_url = (string) ($redirect['sourceUrl'] ?? $redirect['source_url'] ?? '');
            $redirect_id = absint($redirect['id'] ?? 0);
            if ($source_url === '' || $redirect_id <= 0) {
                continue;
            }
            $matches = trailingslashit($source_url) === trailingslashit($full_url)
                || trailingslashit($source_url) === trailingslashit((string) $gone_url);
            if (!$matches) {
                continue;
            }

            $deleted = $auth_manager->make_authenticated_request(
                'api/Redirect/' . $redirect_id . '?projectId=' . $project_id,
                'DELETE'
            );
            return $deleted === false
                ? ' The matching redirect was found, but database synchronization failed.'
                : ' The matching redirect was also removed from the project database.';
        }

        return ' No matching project redirect required database synchronization.';
    }

    public function handle_add_htaccess_gone() {
        // Check for nonce security
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error('Security check failed.');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions.');
            return;
        }
        
        // Get gone URL from POST
        $gone_url = isset($_POST['url']) ? sanitize_text_field((string) wp_unslash($_POST['url'])) : '';
        
        // Validate data
        if (empty($gone_url)) {
            wp_send_json_error('Gone URL is missing.');
            return;
        }
        
        $result = $this->aysa_add_htaccess_gone_rule($gone_url);
        if (empty($result['ok'])) {
            wp_send_json_error((string) ($result['message'] ?? 'Failed to add gone rule.'));
            return;
        }

        $gone_url = (string) ($result['gone_url'] ?? $gone_url);
        $gone_rule = (string) ($result['gone_rule'] ?? '');
        $redirect_exists = !empty($result['redirect_removed']);

        $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
        if ($project_id > 0) {
            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_add_htaccess_gone',
                'add_htaccess_gone',
                array(
                    'module' => 'technical_optimization',
                    'url' => $gone_url,
                    'redirect_removed' => (int) $redirect_exists,
                ),
                12,
                '/v1/technical/gone/htaccess/add'
            );
        }
        
        $api_sync_msg = '';
        if ($redirect_exists) {
            $api_sync_msg = $this->aysa_sync_removed_redirect_for_gone($gone_url);
        }
        
        wp_send_json_success(array(
            'message' => 'Gone rule added successfully to .htaccess file.' . $api_sync_msg,
            'gone_rule' => $gone_rule,
            'redirect_removed' => $redirect_exists
        ));
    }

    /**
     * Handle removing .htaccess redirect
     */
    public function handle_remove_htaccess_redirect() {
        // Check for nonce security
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error('Security check failed.');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions.');
            return;
        }
        
        // Get source URL from POST
        $source = isset($_POST['source']) ? sanitize_text_field((string) wp_unslash($_POST['source'])) : '';
        $result = $this->aysa_remove_htaccess_redirect_rule($source);
        if (empty($result['ok'])) {
            wp_send_json_error((string) ($result['message'] ?? 'Redirect rule not found in .htaccess.'));
            return;
        }

        $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
        if ($project_id > 0) {
            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_remove_htaccess_redirect',
                'remove_htaccess_redirect',
                array(
                    'module' => 'technical_optimization',
                    'source' => $source,
                ),
                12,
                '/v1/technical/redirects/htaccess/remove'
            );
        }

        wp_send_json_success(array(
            'message' => (string) ($result['message'] ?? 'Redirect removed successfully from .htaccess file.'),
        ));
    }
    
    /**
     * Handle removing .htaccess gone URL
     */
    public function handle_remove_htaccess_gone() {
        // Check for nonce security
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error('Security check failed');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions.');
            return;
        }
        
        // Get gone URL from POST
        $gone_url = isset($_POST['url']) ? sanitize_text_field((string) wp_unslash($_POST['url'])) : '';
        
        // Validate data
        if (empty($gone_url)) {
            wp_send_json_error('Gone URL is missing.');
            return;
        }
        
        // Make sure URL starts with a slash
        if (strpos($gone_url, '/') !== 0) {
            $gone_url = '/' . $gone_url;
        }
        
        // Get the path to the root .htaccess file
        $htaccess_file = ABSPATH . '.htaccess';
        
        // Check if file exists and is writable
        if (!file_exists($htaccess_file)) {
            wp_send_json_error('The .htaccess file does not exist in the WordPress root directory.');
            return;
        }
        
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- Admin-authorized WordPress-root file operations retain a fail-closed writability preflight before the guarded write.
        if (!is_writable($htaccess_file)) {
            wp_send_json_error('The .htaccess file is not writable. Please check file permissions.');
            return;
        }
        
        // Get current .htaccess content
        $htaccess_content = file_get_contents($htaccess_file);
        
        // Check if our section exists
        $aysa_section_start = '# BEGIN Aysa Redirects';
        $aysa_section_end = '# END Aysa Redirects';
        
        // Pattern to match any 410 gone rule for this URL
        $gone_pattern = 'RedirectMatch 410 \^' . preg_quote(preg_quote($gone_url, '/'), '/') . '\$';
        
        if (strpos($htaccess_content, $aysa_section_start) !== false) {
            // Section exists, find and remove the gone rule
            $pattern = '/' . preg_quote($aysa_section_start, '/') . '(.*?)' . preg_quote($aysa_section_end, '/') . '/s';
            if (preg_match($pattern, $htaccess_content, $matches)) {
                $existing_content = $matches[1];
                
                // Use regex to remove the line containing our gone rule
                $modified_content = preg_replace('/[\r\n]*' . $gone_pattern . '.*[\r\n]*/i', "\n", $existing_content);
                
                // Update the section with the modified content
                $new_content = $aysa_section_start . $modified_content . $aysa_section_end;
                $htaccess_content = preg_replace($pattern, $new_content, $htaccess_content);
                
                // Write the updated content back to .htaccess
                // phpcs:ignore PluginCheck.CodeAnalysis.WriteFile.ABSPATHDetected -- Admin-authorized redirect operation intentionally updates site-root .htaccess after nonce, capability, path and writability checks.
                $result = file_put_contents($htaccess_file, $htaccess_content);
                
                if ($result === false) {
                    wp_send_json_error('Failed to write to .htaccess file.');
                    return;
                }

                $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
                if ($project_id > 0) {
                    $this->aysa_wallet_track_action_usage(
                        $project_id,
                        'aysa_remove_htaccess_gone',
                        'remove_htaccess_gone',
                        array(
                            'module' => 'technical_optimization',
                            'url' => $gone_url,
                        ),
                        12,
                        '/v1/technical/gone/htaccess/remove'
                    );
                }
                
                wp_send_json_success(array(
                    'message' => 'Gone rule removed successfully from .htaccess file.'
                ));
                return;
            }
        }
        
        // If we got here, either the section doesn't exist or the gone rule wasn't found
        wp_send_json_error('Gone rule not found in .htaccess.');
    }

    /**
     * AJAX: Return all posts, pages, products, categories, tags for redirect suggestions
     */
    public function ajax_get_redirect_suggestions() {
        $provided_nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($provided_nonce, 'aysa_nonce')) {
            wp_send_json_error('Security check failed');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('No permission');
            return;
        }
        $type = isset($_POST['type']) ? sanitize_text_field((string) wp_unslash($_POST['type'])) : '';
        $results = array();
        if ($type === 'post' || $type === '') {
            $posts = get_posts(array('post_type' => 'post', 'numberposts' => 100));
            foreach ($posts as $post) {
                $results[] = array('title' => $post->post_title, 'url' => get_permalink($post->ID));
            }
        }
        if ($type === 'page' || $type === '') {
            $pages = get_posts(array('post_type' => 'page', 'numberposts' => 100));
            foreach ($pages as $page) {
                $results[] = array('title' => $page->post_title, 'url' => get_permalink($page->ID));
            }
        }
        if (($type === 'product' || $type === '') && post_type_exists('product')) {
            $products = get_posts(array('post_type' => 'product', 'numberposts' => 100));
            foreach ($products as $prod) {
                $results[] = array('title' => $prod->post_title, 'url' => get_permalink($prod->ID));
            }
        }
        if ($type === 'category' || $type === '') {
            $cats = get_categories(array('hide_empty' => false, 'number' => 100));
            foreach ($cats as $cat) {
                $results[] = array('title' => $cat->name, 'url' => get_category_link($cat->term_id));
            }
        }
        if ($type === 'tag' || $type === '') {
            $tags = get_tags(array('hide_empty' => false, 'number' => 100));
            foreach ($tags as $tag) {
                $results[] = array('title' => $tag->name, 'url' => get_tag_link($tag->term_id));
            }
        }
        // Remove duplicates by URL
        $results = array_values(array_unique($results, SORT_REGULAR));

        $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
        if ($project_id > 0) {
            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_get_redirect_suggestions',
                'get_redirect_suggestions',
                array(
                    'module' => 'technical_optimization',
                    'type' => $type === '' ? 'all' : $type,
                    'result_count' => count($results),
                ),
                12,
                '/v1/technical/redirects/suggestions'
            );
        }
        wp_send_json_success($results);
    }
    
    /**
     * AJAX handler for saving plugin settings
     */
    public function handle_save_settings() {
        // Check nonce
        check_ajax_referer('aysa_settings_nonce', 'nonce');
        
        // Check user permissions
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'You do not have permission to save settings.'));
            return;
        }
        
        // Get and sanitize settings
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $settings = isset($_POST['settings']) ? wp_unslash($_POST['settings']) : array();
        
        // Sanitize and save project ID
        if (isset($settings['project_id'])) {
            $project_id = intval($settings['project_id']);
            if ($project_id > 0) {
                update_option('aysa_project_id', $project_id);
            }
        }

        if (array_key_exists('adverlink_api_key', $settings)) {
            update_option('aysa_adverlink_api_key', sanitize_text_field((string) $settings['adverlink_api_key']));
        }

        if (array_key_exists('adverlink_advertiser_org_id', $settings)) {
            update_option('aysa_adverlink_advertiser_org_id', sanitize_text_field((string) $settings['adverlink_advertiser_org_id']));
        }
        
        // Sanitize and save auto scan setting
        if (isset($settings['auto_scan'])) {
            $auto_scan = sanitize_text_field((string) $settings['auto_scan']);
            $allowed_values = array('daily', 'weekly', 'monthly', 'disabled');
            if (in_array($auto_scan, $allowed_values)) {
                update_option('aysa_auto_scan', $auto_scan);
            }
        }
        
        // Sanitize and save notification settings
        if (isset($settings['notify_scan'])) {
            update_option('aysa_notify_scan', (bool) $settings['notify_scan']);
        }
        
        if (isset($settings['notify_issues'])) {
            update_option('aysa_notify_issues', (bool) $settings['notify_issues']);
        }
        
        if (isset($settings['notify_updates'])) {
            update_option('aysa_notify_updates', (bool) $settings['notify_updates']);
        }
        
        // Sanitize and save uninstall table deletion setting
        if (isset($settings['delete_tables_on_uninstall'])) {
            update_option('aysa_delete_tables_on_uninstall', (bool) $settings['delete_tables_on_uninstall']);
        }
        
        // Return success
        wp_send_json_success(array('message' => 'Settings saved successfully.'));
    }
    
    public function handle_get_feature_stats() {
        check_ajax_referer('aysa_automation_nonce', 'nonce');
        
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'You do not have permission to perform this action.'));
            return;
        }
        
        $feature_type = isset($_POST['feature_type']) ? sanitize_text_field((string) wp_unslash($_POST['feature_type'])) : '';
        
        global $wpdb;
        $audit_table = esc_sql($wpdb->prefix . 'aysa_audit_log');
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $total_suggestions = $wpdb->get_var($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT COUNT(*) FROM {$audit_table} WHERE action_type = %s AND is_automated = 0",
            $feature_type . '_suggestion'
        ));
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $accepted_suggestions = $wpdb->get_var($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT COUNT(*) FROM {$audit_table} WHERE action_type = %s AND is_automated = 0",
            $feature_type . '_accepted'
        ));
        
        $acceptance_rate = $total_suggestions > 0 ? round(($accepted_suggestions / $total_suggestions) * 100) : 0;
        
        wp_send_json_success(array(
            'acceptance_rate' => $acceptance_rate,
            'suggestions_count' => $total_suggestions
        ));
    }
    
    public function handle_check_acceptance_rate() {
        check_ajax_referer('aysa_automation_nonce', 'nonce');
        
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'You do not have permission to perform this action.'));
            return;
        }
        
        $feature_type = isset($_POST['feature_type']) ? sanitize_text_field((string) wp_unslash($_POST['feature_type'])) : '';
        
        global $wpdb;
        $audit_table = esc_sql($wpdb->prefix . 'aysa_audit_log');
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $total_suggestions = $wpdb->get_var($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT COUNT(*) FROM {$audit_table} WHERE action_type = %s AND is_automated = 0",
            $feature_type . '_suggestion'
        ));
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $accepted_suggestions = $wpdb->get_var($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT COUNT(*) FROM {$audit_table} WHERE action_type = %s AND is_automated = 0",
            $feature_type . '_accepted'
        ));
        
        $acceptance_rate = $total_suggestions > 0 ? ($accepted_suggestions / $total_suggestions) * 100 : 0;
        $meets_threshold = $acceptance_rate >= 85;
        
        wp_send_json_success(array(
            'meets_threshold' => $meets_threshold,
            'acceptance_rate' => round($acceptance_rate)
        ));
    }
    
    public function handle_save_automation_setting() {
        check_ajax_referer('aysa_automation_nonce', 'nonce');
        
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'You do not have permission to perform this action.'));
            return;
        }
        
        $setting_name = isset($_POST['setting_name']) ? sanitize_text_field((string) wp_unslash($_POST['setting_name'])) : '';
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $setting_value = isset($_POST['setting_value']) ? wp_unslash($_POST['setting_value']) : '';
        
        if (is_bool($setting_value) || $setting_value === 'true' || $setting_value === 'false') {
            $setting_value = filter_var($setting_value, FILTER_VALIDATE_BOOLEAN);
        }
        
        update_option('aysa_' . $setting_name, $setting_value);
        
        wp_send_json_success(array('message' => 'Setting saved successfully.'));
    }
    
    public function handle_save_qa_settings() {
        check_ajax_referer('aysa_automation_nonce', 'nonce');
        
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'You do not have permission to perform this action.'));
            return;
        }
        
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $settings = isset($_POST['settings']) ? wp_unslash($_POST['settings']) : array();
        
        foreach ($settings as $key => $value) {
            $sanitized_key = sanitize_text_field($key);
            
            if (in_array($sanitized_key, ['auto_approve_threshold', 'suggest_threshold', 'manual_review_threshold'])) {
                $sanitized_value = floatval($value);
            } else {
                $sanitized_value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
            }
            
            update_option('aysa_' . $sanitized_key, $sanitized_value);
        }
        
        wp_send_json_success(array('message' => 'QA settings saved successfully.'));
    }
    
    public function handle_save_reporting_settings() {
        check_ajax_referer('aysa_settings_nonce', 'nonce');
        
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'You do not have permission to save settings.'));
            return;
        }
        
        if (isset($_POST['aysa_reporting_mode'])) {
            update_option('aysa_reporting_mode', sanitize_text_field((string) wp_unslash($_POST['aysa_reporting_mode'])));
        }
        if (isset($_POST['aysa_reporting_endpoint'])) {
            update_option('aysa_reporting_endpoint', esc_url_raw((string) wp_unslash($_POST['aysa_reporting_endpoint'])));
        }
        if (isset($_POST['aysa_reporting_enabled'])) {
            update_option('aysa_reporting_enabled', 1);
        } else {
            update_option('aysa_reporting_enabled', 0);
        }
        
        wp_send_json_success(array('message' => 'Reporting settings saved successfully.'));
    }
    
    public function handle_submit_feedback() {
        check_ajax_referer('aysa_feedback_nonce', 'nonce');
        
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'You do not have permission to submit feedback.'));
            return;
        }
        
        $config = Aysa_Automation_Config::get_feedback_config();
        
        $feedback_data = array(
            'category' => isset($_POST['category']) ? sanitize_text_field((string) wp_unslash($_POST['category'])) : '',
            'message' => isset($_POST['message']) ? sanitize_textarea_field((string) wp_unslash($_POST['message'])) : '',
            'site_url' => isset($_POST['site_url']) ? sanitize_url((string) wp_unslash($_POST['site_url'])) : '',
            'plugin_version' => isset($_POST['plugin_version']) ? sanitize_text_field((string) wp_unslash($_POST['plugin_version'])) : '',
            'user_id' => isset($_POST['user_id']) ? intval((string) wp_unslash($_POST['user_id'])) : 0,
            'timestamp' => current_time('mysql'),
            'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field((string) wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '',
            'wp_version' => get_bloginfo('version'),
            'php_version' => PHP_VERSION
        );
        
        $response = wp_remote_post($config['endpoint'], array(
            'timeout' => $config['timeout'],
            'headers' => array(
                'Content-Type' => 'application/json',
                'User-Agent' => 'Aysa-Plugin/' . (defined('AYSA_VERSION') ? AYSA_VERSION : '1.0.0')
            ),
            'body' => json_encode($feedback_data)
        ));
        
        if (is_wp_error($response)) {
            aysa_debug_log('Aysa Feedback Error: ' . $response->get_error_message());
            wp_send_json_error(array('message' => 'Failed to send feedback. Please try again later.'));
        } else {
            $response_code = wp_remote_retrieve_response_code($response);
            if ($response_code >= 200 && $response_code < 300) {
                wp_send_json_success(array('message' => 'Feedback sent successfully.'));
            } else {
                aysa_debug_log('Aysa Feedback HTTP Error: ' . $response_code);
                wp_send_json_error(array('message' => 'Server error. Please try again later.'));
            }
        }
    }

    /**
     * Handle social media content generation AJAX request
     */
    public function handle_generate_social_content() {
        // Check nonce for security
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_task_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }

        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions.'));
            return;
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $platforms = isset($_POST['platforms']) ? wp_unslash($_POST['platforms']) : (isset($_POST['platform']) ? array(sanitize_key(wp_unslash($_POST['platform']))) : array());
        $appname = sanitize_text_field(wp_unslash($_POST['appname'] ?? '')); // For future API integration
        
        if (empty($platforms)) {
            wp_send_json_error(array('message' => 'At least one platform is required.'));
            return;
        }

        $platform_limits = array(
            'facebook' => 63206,
            'linkedin' => 3000,
            'x' => 280,
            'instagram' => 2200,
            'youtube' => 5000,
            'pinterest' => 500
        );

        $results = array();
        
        foreach ($platforms as $platform) {
            $platform = sanitize_text_field($platform);
            
            if (!isset($platform_limits[$platform])) {
                continue; // Skip invalid platforms
            }

            $limit = $platform_limits[$platform];
            $content = $this->generate_platform_content($platform, $limit);
            
            $results[] = array(
                'platform' => $platform,
                'content' => $content,
                'character_count' => strlen($content),
                'character_limit' => $limit
            );
        }

        if (count($results) === 1 && isset($_POST['platform'])) {
            wp_send_json_success(array(
                'content' => $results[0]['content'],
                'platform' => $results[0]['platform'],
                'character_count' => $results[0]['character_count'],
                'character_limit' => $results[0]['character_limit']
            ));
        } else {
            wp_send_json_success(array(
                'results' => $results,
                'appname' => $appname // Echo back for future use
            ));
        }
    }

    /**
     * Generate platform-specific lorem ipsum content
     */
    private function generate_platform_content($platform, $limit) {
        $base_content = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
        
        $platform_specific = array(
            'facebook' => array(
                'prefix' => "🚀 Exciting news! ",
                'suffix' => "\n\n#Marketing #Business #Growth #Success #Innovation #DigitalMarketing #SocialMedia #Entrepreneur #Leadership #Technology",
                'cta' => "\n\n👉 What do you think? Share your thoughts in the comments!"
            ),
            'linkedin' => array(
                'prefix' => "Professional insight: ",
                'suffix' => "\n\n#LinkedIn #Professional #Business #Career #Industry #Networking #Leadership #Innovation #Strategy #Growth",
                'cta' => "\n\nWhat's your experience with this? I'd love to hear your thoughts."
            ),
            'x' => array(
                'prefix' => "🔥 ",
                'suffix' => " #trending #viral #tech",
                'cta' => ""
            ),
            'instagram' => array(
                'prefix' => "✨ ",
                'suffix' => "\n\n#Instagram #Lifestyle #Inspiration #Creative #Beautiful #Amazing #Love #Follow #PhotoOfTheDay #InstaGood",
                'cta' => "\n\n💬 Tag a friend who needs to see this!"
            ),
            'youtube' => array(
                'prefix' => "🎥 New content alert! ",
                'suffix' => "\n\n#YouTube #Content #Video #Subscribe #Like #Share #Creator #Entertainment #Educational #Tutorial",
                'cta' => "\n\n👍 Don't forget to like and subscribe for more content!"
            ),
            'pinterest' => array(
                'prefix' => "📌 ",
                'suffix' => " #Pinterest #DIY #Ideas #Inspiration #Creative",
                'cta' => ""
            )
        );

        $config = $platform_specific[$platform];
        $content = $config['prefix'] . $base_content . $config['cta'] . $config['suffix'];

        if (strlen($content) > $limit) {
            $available_space = $limit - strlen($config['prefix']) - strlen($config['suffix']) - strlen($config['cta']);
            $trimmed_base = substr($base_content, 0, $available_space - 3) . "...";
            $content = $config['prefix'] . $trimmed_base . $config['cta'] . $config['suffix'];
        }

        return $content;
    }
    
    
    public function handle_get_scheduled_tasks() {
        if (class_exists('Aysa_Scheduler')) {
            $scheduler = new Aysa_Scheduler();
            $scheduler->handle_get_scheduled_tasks();
        } else {
            wp_send_json_error('Scheduler not available');
        }
    }

    public function handle_schedule_domain_authority_task() {
        $this->handle_get_pending_domain_authority_reviews();
    }
    
    public function handle_get_pending_domain_authority_reviews() {
        aysa_debug_log('[AYSA DOMAIN AUTHORITY] Received request for pending reviews');
        check_ajax_referer('aysa_automation_nonce', 'nonce');
        
        if (!current_user_can('manage_options')) {
            aysa_debug_log('[AYSA DOMAIN AUTHORITY] Unauthorized user attempted to access pending reviews');
            aysa_debug_log('[AYSA DOMAIN AUTHORITY] Unauthorized user attempted to schedule task');
            wp_send_json_error('Insufficient permissions');
        }
        
        $automation_config = get_option('aysa_automation_config', array());
        if (!isset($automation_config['domain_authority_automation']) || !$automation_config['domain_authority_automation']) {
            aysa_debug_log('[AYSA DOMAIN AUTHORITY] Task scheduling rejected - automation not enabled');
            wp_send_json_error('Domain Authority automation is disabled');
        }
        
        $project_id = intval(get_option('aysa_project_id', 0));
        $requested_project_id = isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0;
        $mean_backlinks = isset($_POST['mean_backlinks']) ? intval((string) wp_unslash($_POST['mean_backlinks'])) : 0;
        $mean_authority = isset($_POST['mean_authority']) ? intval((string) wp_unslash($_POST['mean_authority'])) : 0;
        $competitor_data = isset($_POST['competitor_data'])
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
            ? json_decode((string) wp_unslash($_POST['competitor_data']), true)
            : array();
        
        aysa_debug_log('[AYSA DOMAIN AUTHORITY] Task data received - Project: ' . $project_id . ', Backlinks: ' . $mean_backlinks . ', Authority: ' . $mean_authority);
        
        if (!$project_id || ($requested_project_id > 0 && $requested_project_id !== $project_id) || !$mean_backlinks || !$mean_authority) {
            aysa_debug_log('[AYSA DOMAIN AUTHORITY] Invalid task data provided');
            wp_send_json_error('Invalid task data');
        }
        
        $task_data = array(
            'title' => "Get {$mean_backlinks} links with {$mean_authority}+ authority",
            'description' => 'Domain authority optimization task based on competitor analysis',
            'target_backlinks' => $mean_backlinks,
            'target_authority' => $mean_authority,
            'competitor_data' => $competitor_data,
            'project_id' => $project_id,
            'created_at' => current_time('mysql')
        );
        
        aysa_debug_log('[AYSA DOMAIN AUTHORITY] Prepared task data: ' . json_encode($task_data));
        
        $queue_manager = new Aysa_Queue_Manager();
        $queue_manager->init();
        
        aysa_debug_log('[AYSA DOMAIN AUTHORITY] Adding task to queue');
        $result = $queue_manager->add_job('domain_authority_processor', $task_data, 5);
        
        if (is_wp_error($result)) {
            aysa_debug_log('[AYSA DOMAIN AUTHORITY] Failed to add task to queue: ' . $result->get_error_message());
            wp_send_json_error($result->get_error_message());
        }
        
        aysa_debug_log('[AYSA DOMAIN AUTHORITY] Task scheduled successfully with job ID: ' . $result);
        wp_send_json_success(array(
            'job_id' => $result,
            'message' => 'Domain Authority task scheduled successfully'
        ));
    }
    
    public function handle_schedule_association_task() {
        aysa_debug_log('[AYSA DOMAIN AUTHORITY] Received association task scheduling request');
        
        if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'] ?? '')), 'aysa_automation_nonce')) {
            aysa_debug_log('[AYSA DOMAIN AUTHORITY] Invalid nonce for association task scheduling');
            wp_send_json_error('Invalid nonce');
        }
        
        if (!current_user_can('manage_options')) {
            aysa_debug_log('[AYSA DOMAIN AUTHORITY] Unauthorized user attempted to schedule association task');
            wp_send_json_error('Insufficient permissions');
        }
        
        $automation_config = get_option('aysa_automation_config', array());
        if (!isset($automation_config['domain_authority_automation']) || !$automation_config['domain_authority_automation']) {
            aysa_debug_log('[AYSA DOMAIN AUTHORITY] Association task scheduling rejected - automation not enabled');
            wp_send_json_error('Domain Authority automation is disabled');
        }
        
        $project_id = intval(get_option('aysa_project_id', 0));
        $requested_project_id = isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0;
        $association_count = isset($_POST['association_count']) ? intval((string) wp_unslash($_POST['association_count'])) : 0;
        
        aysa_debug_log('[AYSA DOMAIN AUTHORITY] Association task data received - Project: ' . $project_id . ', Count: ' . $association_count);
        
        if (!$project_id || ($requested_project_id > 0 && $requested_project_id !== $project_id) || !$association_count) {
            aysa_debug_log('[AYSA DOMAIN AUTHORITY] Invalid association task data provided');
            wp_send_json_error('Invalid task data');
        }
        
        $task_data = array(
            'title' => "Associate {$association_count} keywords with pages/products/categories",
            'description' => 'Keyword association task based on available unassociated keywords',
            'association_count' => $association_count,
            'project_id' => $project_id,
            'created_at' => current_time('mysql')
        );
        
        aysa_debug_log('[AYSA DOMAIN AUTHORITY] Prepared association task data: ' . json_encode($task_data));
        
        $queue_manager = new Aysa_Queue_Manager();
        $queue_manager->init();
        
        aysa_debug_log('[AYSA DOMAIN AUTHORITY] Adding association task to queue');
        $result = $queue_manager->add_job('association_processor', $task_data, 5);
        
        if (is_wp_error($result)) {
            aysa_debug_log('[AYSA DOMAIN AUTHORITY] Failed to add association task to queue: ' . $result->get_error_message());
            wp_send_json_error($result->get_error_message());
        }
        
        aysa_debug_log('[AYSA DOMAIN AUTHORITY] Association task scheduled successfully with job ID: ' . $result);
        wp_send_json_success(array(
            'job_id' => $result,
            'message' => 'Association task scheduled successfully'
        ));
    }
    
    public function handle_add_task_to_queue() {
        aysa_debug_log('[AYSA AUTOMATION] Received task queue addition request');
        
        if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'] ?? '')), 'aysa_automation_nonce')) {
            aysa_debug_log('[AYSA AUTOMATION] Invalid nonce for task queue addition');
            wp_send_json_error('Invalid nonce');
        }
        
        if (!current_user_can('manage_options')) {
            aysa_debug_log('[AYSA AUTOMATION] Unauthorized user attempted to add task to queue');
            wp_send_json_error('Insufficient permissions');
        }
        
        $task_id = isset($_POST['task_id']) ? sanitize_text_field((string) wp_unslash($_POST['task_id'])) : '';
        $task_title = isset($_POST['task_title']) ? sanitize_text_field((string) wp_unslash($_POST['task_title'])) : '';
        $processor = isset($_POST['processor']) ? sanitize_key((string) wp_unslash($_POST['processor'])) : 'association_processor';
        $priority = max(1, min(10, isset($_POST['priority']) ? intval((string) wp_unslash($_POST['priority'])) : 5));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $task_data_json = isset($_POST['task_data']) ? (string) wp_unslash($_POST['task_data']) : '';
        
        aysa_debug_log('[AYSA AUTOMATION] Task queue data received - ID: ' . $task_id . ', Title: ' . $task_title . ', Processor: ' . $processor);
        
        if (!$task_id || $processor !== 'association_processor') {
            aysa_debug_log('[AYSA AUTOMATION] Invalid task ID provided');
            wp_send_json_error('Invalid or unsupported automation task');
            return;
        }

        $project_id = intval(get_option('aysa_project_id', 0));
        if ($project_id <= 0) {
            wp_send_json_error('Project is not configured for this WordPress installation');
            return;
        }
        
        $task_data = array(
            'task_id' => $task_id,
            'title' => $task_title,
            'processor_type' => $processor,
            'source' => 'dotnet_api',
            'created_at' => current_time('mysql')
        );
        
        if (!empty($task_data_json)) {
            $parsed_task_data = json_decode($task_data_json, true);
            if ($parsed_task_data) {
                $task_data = array_merge($task_data, $parsed_task_data);
                aysa_debug_log('[AYSA AUTOMATION] Merged task data from .NET Core API');
            }
        }
        $task_data['project_id'] = $project_id;
        $task_data['projectId'] = $project_id;
        $task_data['processor_type'] = $processor;
        
        aysa_debug_log('[AYSA AUTOMATION] Prepared task data for queue: ' . json_encode($task_data));
        
        $queue_manager = new Aysa_Queue_Manager();
        $queue_manager->init();
        
        aysa_debug_log('[AYSA AUTOMATION] Adding task to automation queue with processor: ' . $processor);
        $result = $queue_manager->add_job($processor, $task_data, $priority);
        
        if (is_wp_error($result)) {
            aysa_debug_log('[AYSA AUTOMATION] Failed to add task to queue: ' . $result->get_error_message());
            wp_send_json_error($result->get_error_message());
        }
        
        aysa_debug_log('[AYSA AUTOMATION] Task added to queue successfully with job ID: ' . $result);
        wp_send_json_success(array(
            'job_id' => $result,
            'task_id' => $task_id,
            'message' => 'Task added to automation queue successfully'
        ));
    }
    
    public function handle_add_pending_review() {
        check_ajax_referer('aysa_automation_nonce', 'nonce');
        
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }
        
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $review_data = wp_unslash($_POST['review_data'] ?? array());
        
        if (empty($review_data)) {
            wp_send_json_error('No review data provided');
            return;
        }
        
        $existing_reviews = get_option('aysa_pending_dotnet_reviews', array());
        $existing_reviews[$review_data['task_id']] = $review_data;
        
        $result = update_option('aysa_pending_dotnet_reviews', $existing_reviews);
        
        if ($result) {
            wp_send_json_success('Task added to pending reviews');
        } else {
            wp_send_json_error('Failed to add task to pending reviews');
        }
    }
    
    public function handle_get_pending_dotnet_reviews() {
        check_ajax_referer('aysa_automation_nonce', 'nonce');
        
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }
        
        $pending_reviews = get_option('aysa_pending_dotnet_reviews', array());
        
        wp_send_json_success($pending_reviews);
    }
    
    public function handle_approve_dotnet_task() {
        check_ajax_referer('aysa_automation_nonce', 'nonce');
        
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }
        
        $task_id = sanitize_text_field(wp_unslash($_POST['task_id'] ?? ''));
        if (empty($task_id)) {
            wp_send_json_error('No task ID provided');
            return;
        }
        
        $pending_reviews = get_option('aysa_pending_dotnet_reviews', array());
        if (isset($pending_reviews[$task_id])) {
            $task_data = $pending_reviews[$task_id];
            unset($pending_reviews[$task_id]);
            update_option('aysa_pending_dotnet_reviews', $pending_reviews);
            
            $queue_manager = new Aysa_Queue_Manager();
            $queue_manager->init();
            $result = $queue_manager->add_job('research_processor', $task_data['task_data'], 3);
            
            wp_send_json_success('Task approved and added to queue');
        } else {
            wp_send_json_error('Task not found in pending reviews');
        }
    }
    
    public function handle_reject_dotnet_task() {
        check_ajax_referer('aysa_automation_nonce', 'nonce');
        
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }
        
        $task_id = sanitize_text_field(wp_unslash($_POST['task_id'] ?? ''));
        if (empty($task_id)) {
            wp_send_json_error('No task ID provided');
            return;
        }
        
        $pending_reviews = get_option('aysa_pending_dotnet_reviews', array());
        if (isset($pending_reviews[$task_id])) {
            unset($pending_reviews[$task_id]);
            update_option('aysa_pending_dotnet_reviews', $pending_reviews);
            wp_send_json_success('Task rejected and removed from pending reviews');
        } else {
            wp_send_json_error('Task not found in pending reviews');
        }
    }
    
    public function handle_update_status() {
        check_ajax_referer('aysa_nonce', 'nonce');
        
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }
        
        $association_id = intval(wp_unslash($_POST['association_id'] ?? 0));
        $new_status = sanitize_text_field(wp_unslash($_POST['status'] ?? ''));
        
        if (!$association_id || !$new_status) {
            wp_send_json_error('Invalid association ID or status');
            return;
        }
        
        // For now, we'll store the status in WordPress options
        // In a real implementation, this would update a database table
        $association_statuses = get_option('aysa_association_statuses', array());
        $association_statuses[$association_id] = $new_status;
        
        $result = update_option('aysa_association_statuses', $association_statuses);
        
        if ($result) {
            wp_send_json_success(array(
                'message' => 'Status updated successfully',
                'association_id' => $association_id,
                'new_status' => $new_status
            ));
        } else {
            wp_send_json_error('Failed to update status');
        }
    }
    
    /**
     * Handle getting link orders
     */
    public function handle_get_link_orders() {
        // Check nonce
        if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'] ?? '')), 'aysa_orders_nonce')) {
            wp_send_json_error('Security check failed');
        }
        
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
        }
        
        $project_id = intval(wp_unslash($_POST['project_id'] ?? 0));
        $status_filter = sanitize_text_field(wp_unslash($_POST['status_filter'] ?? ''));
        
        if (!$project_id) {
            wp_send_json_error('Invalid project ID');
        }
        
        global $wpdb;
        $table_name = esc_sql($wpdb->prefix . 'aysa_link_purchases');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $existing_order = $wpdb->get_row($wpdb->prepare("SELECT id, project_id, status FROM {$table_name} WHERE id = %d", $order_id));
        $project_id = intval($existing_order->project_id ?? 0);
        if ($project_id <= 0) {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
        }
        
        // Build query based on status filter
        $where_clause = "WHERE project_id = %d";
        $params = array($project_id);
        
        if ($status_filter !== 'all') {
            $where_clause .= " AND status = %s";
            $params[] = $status_filter;
        }
        
        $query = "SELECT * FROM {$table_name} {$where_clause} ORDER BY created_at DESC";
        
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query: table identifiers and structure are internal, and request-derived values are prepared before execution. Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $orders = $wpdb->get_results($wpdb->prepare($query, $params));
        
        if ($orders === false) {
            aysa_debug_log('[AYSA ORDERS] Database error: ' . $wpdb->last_error);
            wp_send_json_error('Database error occurred');
        }
        
        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_get_link_orders',
            'offpage_get_link_orders',
            array(
                'module' => 'offpage_legacy',
                'status_filter' => $status_filter,
                'count' => is_array($orders) ? count($orders) : 0,
            ),
            2,
            'wp_ajax/aysa_get_link_orders'
        );

        wp_send_json_success($orders);
    }
    
    /**
     * Handle getting single order details
     */
    public function handle_get_order_details() {
        // Check nonce
        if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'] ?? '')), 'aysa_orders_nonce')) {
            wp_send_json_error('Security check failed');
        }
        
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
        }
        
        $order_id = intval(wp_unslash($_POST['order_id'] ?? 0));
        
        if (!$order_id) {
            wp_send_json_error('Invalid order ID');
        }
        
        global $wpdb;
        $table_name = esc_sql($wpdb->prefix . 'aysa_link_purchases');
        
        $query = "SELECT * FROM {$table_name} WHERE id = %d";
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query: table identifiers and structure are internal, and request-derived values are prepared before execution. Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $order = $wpdb->get_row($wpdb->prepare($query, $order_id));
        
        if (!$order) {
            wp_send_json_error('Order not found');
        }
        
        $project_id = intval($order->project_id ?? 0);
        if ($project_id <= 0) {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
        }

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_get_order_details',
            'offpage_get_order_details_' . $order_id,
            array(
                'module' => 'offpage_legacy',
                'order_id' => $order_id,
            ),
            1,
            'wp_ajax/aysa_get_order_details'
        );

        wp_send_json_success($order);
    }
    
    /**
     * Handle retrying link order
     */
    public function handle_retry_link_order() {
        // Check nonce
        if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'] ?? '')), 'aysa_orders_nonce')) {
            wp_send_json_error('Security check failed');
        }
        
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
        }
        
        $order_id = intval(wp_unslash($_POST['order_id'] ?? 0));
        
        if (!$order_id) {
            wp_send_json_error('Invalid order ID');
        }
        
        global $wpdb;
        $table_name = esc_sql($wpdb->prefix . 'aysa_link_purchases');
        
        // Update order status to pending to retry
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $result = $wpdb->update(
            $table_name,
            array(
                'status' => 'pending',
                'updated_at' => current_time('mysql')
            ),
            array('id' => $order_id),
            array('%s', '%s'),
            array('%d')
        );
        
        if ($result === false) {
            aysa_debug_log('[AYSA ORDERS] Database update error: ' . $wpdb->last_error);
            wp_send_json_error('Failed to update order status');
        }
        
        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_retry_link_order',
            'offpage_retry_link_order_' . $order_id,
            array(
                'module' => 'offpage_legacy',
                'order_id' => $order_id,
                'previous_status' => (string) ($existing_order->status ?? ''),
            ),
            8,
            'wp_ajax/aysa_retry_link_order'
        );

        wp_send_json_success('Order marked for retry');
    }
    
    /**
     * Enqueue credit analytics scripts and styles
     */
    public function enqueue_credit_analytics_scripts($hook = '') {
        $screen = function_exists('get_current_screen') ? get_current_screen() : null;
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed callback inventory: this read-only/core lifecycle request is authenticated by WordPress or an enclosing guard and performs no unapproved write.
        $page = isset($_GET['page']) ? sanitize_key((string) wp_unslash($_GET['page'])) : '';
        $is_settings_page = ($page === 'aysa-settings')
            || ($screen && isset($screen->id) && is_string($screen->id) && strpos($screen->id, 'aysa-settings') !== false)
            || ($screen && isset($screen->base) && is_string($screen->base) && strpos($screen->base, 'aysa-settings') !== false);

        if (!$is_settings_page) {
            return;
        }
        
        // Enqueue Chart.js for the credit breakdown chart
        wp_enqueue_script(
            'chart-js',
            AYSA_PLUGIN_URL . 'assets/js/vendor/chartjs-stub.js',
            array(),
            AYSA_VERSION,
            true
        );
        
        // Enqueue credit analytics CSS
        wp_enqueue_style(
            'aysa-credit-analytics',
            AYSA_PLUGIN_URL . 'assets/css/aysa-credit-analytics.css',
            array(),
            AYSA_VERSION,
            'all'
        );
        
        // Enqueue credit analytics JavaScript
        wp_enqueue_script(
            'aysa-credit-analytics',
            AYSA_PLUGIN_URL . 'assets/js/aysa-credit-analytics.js',
            array('jquery', 'chart-js'),
            AYSA_VERSION,
            true
        );
        
        // Localize script with AJAX data
        wp_localize_script(
            'aysa-credit-analytics',
            'aysa_credit_analytics',
            array(
                'ajax_url' => admin_url('admin-ajax.php'),
                'nonce' => wp_create_nonce('aysa_credit_analytics_nonce'),
                'api_base_url' => $this->get_api_base_url()
            )
        );
    }
    
    /**
     * Get API base URL from settings
     */
    private function get_api_base_url() {
        if (class_exists('Aysa_API_Config')) {
            return rtrim((string) Aysa_API_Config::get_api_base_url(), '/');
        }
        return rtrim((string) AYSA_API_BASE_URL, '/');
    }

    /**
     * Handle AJAX request to get WordToLink API URL
     */
    public function handle_get_wordtolink_api_url() {
        // Verify nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }

        // Get parameters
        $user_id = sanitize_text_field(wp_unslash($_POST['user_id'] ?? ''));
        $url_part = sanitize_text_field(wp_unslash($_POST['url_part'] ?? ''));
        $project_id = $this->aysa_resolve_project_id_with_local_fallback(intval(wp_unslash($_POST['project_id'] ?? 0)));

        if (empty($user_id) || empty($url_part)) {
            wp_send_json_error('Missing required parameters');
            return;
        }

        // Use the centralized API configuration
        if (!class_exists('Aysa_API_Config')) {
            require_once AYSA_PLUGIN_DIR . 'includes/config/aysa-api-config.php';
        }

        try {
            // Get the full API URL using the centralized configuration
            $api_url = Aysa_API_Config::get_endpoint_url_with_params(
                'wordtolink_search',
                array(
                    'userId' => $user_id,
                    'urlPart' => $url_part
                )
            );

            if ($api_url) {
                $this->aysa_wallet_track_action_usage(
                    $project_id,
                    'aysa_get_wordtolink_api_url',
                    'offpage_get_wordtolink_api_url',
                    array(
                        'module' => 'offpage_legacy',
                        'user_id' => $user_id,
                        'url_part' => $url_part,
                    ),
                    1,
                    'wp_ajax/aysa_get_wordtolink_api_url'
                );

                wp_send_json_success(array(
                    'api_url' => $api_url,
                    'environment' => Aysa_API_Config::get_environment(),
                    'base_url' => Aysa_API_Config::get_api_base_url()
                ));
            } else {
                wp_send_json_error('Failed to generate API URL');
            }
        } catch (Exception $e) {
            wp_send_json_error('Error: ' . $e->getMessage());
        }
    }

    /**
     * Handle AJAX request: list link building portals from Adverlink marketplace API.
     */
    public function handle_adverlink_list_portals() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }

        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        $creds = $this->aysa_get_adverlink_credentials();
        $api_key = $creds['api_key'];
        $advertiser_org_id = $creds['org_id'];
        $service_type = sanitize_text_field((string) wp_unslash($_POST['service_type'] ?? ''));
        $billing_type = sanitize_text_field((string) wp_unslash($_POST['billing_type'] ?? ''));
        $per_page = max(1, min(100, intval(wp_unslash($_POST['per_page'] ?? 20))));
        $force_refresh = !empty($_POST['force_refresh']) && $_POST['force_refresh'] !== '0';

        $credentials_check = $this->aysa_validate_adverlink_marketplace_credentials($creds);
        if (is_wp_error($credentials_check)) {
            wp_send_json_error($this->aysa_format_adverlink_marketplace_error($credentials_check));
            return;
        }

        $cache_key = 'aysa_adverlink_portals_' . md5(
            implode('|', array(
                $project_id,
                $service_type,
                $billing_type,
                $per_page
            ))
        );
        if (!$force_refresh) {
            $cached = get_transient($cache_key);
            if (is_array($cached) && isset($cached['items'])) {
                wp_send_json_success(array(
                    'items' => $cached['items'],
                    'meta' => isset($cached['meta']) && is_array($cached['meta']) ? $cached['meta'] : array(),
                    'raw' => $cached['raw'],
                    'cached' => true
                ));
                return;
            }
        }

        $decoded = $this->aysa_broker_request('POST', '/adverlink/v1/public/portals', array(
            'project_id' => $project_id,
            'api_key' => $api_key,
            'advertiser_org_id' => $advertiser_org_id,
            'service_type' => $service_type,
            'billing_type' => $billing_type,
            'per_page' => $per_page,
        ), 45);

        if (is_wp_error($decoded)) {
            wp_send_json_error(array(
                'code' => 'adverlink_api_error',
                'message' => $decoded->get_error_message()
            ));
            return;
        }

        $items = array();
        if (is_array($decoded)) {
            if (isset($decoded['data']) && is_array($decoded['data'])) {
                $items = $decoded['data'];
            } elseif (isset($decoded['items']) && is_array($decoded['items'])) {
                $items = $decoded['items'];
            } elseif (isset($decoded['portals']) && is_array($decoded['portals'])) {
                $items = $decoded['portals'];
            } elseif (array_keys($decoded) === range(0, count($decoded) - 1)) {
                $items = $decoded;
            }
        }

        $marketplace_meta = isset($decoded['meta']) && is_array($decoded['meta'])
            ? $decoded['meta']
            : (isset($decoded['raw']['meta']) && is_array($decoded['raw']['meta']) ? $decoded['raw']['meta'] : array());

        set_transient($cache_key, array('items' => $items, 'meta' => $marketplace_meta, 'raw' => $decoded), 15 * MINUTE_IN_SECONDS);
        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_adverlink_list_portals',
            'offpage_list_portals_' . $project_id,
            array(
                'module' => 'offpage',
                'cached' => false,
                'service_type' => $service_type,
                'billing_type' => $billing_type,
                'items_count' => count($items),
            ),
            0,
            'wp_ajax/aysa_adverlink_list_portals'
        );
        wp_send_json_success(array(
            'items' => $items,
            'meta' => $marketplace_meta,
            'raw' => $decoded,
            'cached' => false
        ));
    }

    /**
     * Handle AJAX request: run backlink audit against configured API endpoint (DataForSEO proxy style).
     */
    public function handle_adverlink_backlink_audit() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }

        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $domain = sanitize_text_field((string) wp_unslash($_POST['domain'] ?? ''));
        $limit = max(1, min(500, intval(wp_unslash($_POST['limit'] ?? 50))));
        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        $force_refresh = !empty($_POST['force_refresh']) && intval(wp_unslash($_POST['force_refresh'])) === 1;

        if (empty($domain)) {
            wp_send_json_error('Target domain is required.');
            return;
        }

        $result = $this->aysa_adjustments_run_offpage_backlink_audit($project_id, $domain, $limit, $force_refresh);
        if (is_wp_error($result)) {
            wp_send_json_error($result->get_error_message());
            return;
        }

        wp_send_json_success($result);
    }

    public function handle_adverlink_get_backlink_audit() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }

        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        $snapshot = get_option($this->aysa_get_backlink_audit_key($project_id), array());
        $last_sync = get_option($this->aysa_get_backlink_audit_sync_key($project_id), array());
        if (!is_array($snapshot) || empty($snapshot)) {
            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_adverlink_get_backlink_audit',
                'offpage_get_backlink_audit_empty_' . $project_id,
                array(
                    'module' => 'offpage',
                    'items_count' => 0,
                ),
                0,
                'wp_ajax/aysa_adverlink_get_backlink_audit'
            );
            wp_send_json_success(array(
                'items' => array(),
                'snapshot' => array(),
                'last_sync' => array(),
            ));
            return;
        }

        wp_send_json_success(array(
            'items' => isset($snapshot['items']) && is_array($snapshot['items']) ? $snapshot['items'] : array(),
            'snapshot' => $snapshot,
            'last_sync' => is_array($last_sync) ? $last_sync : array(),
        ));
    }

    private function aysa_adjustments_run_offpage_backlink_audit($project_id, $domain, $limit = 50, $force_refresh = false) {
        $project_id = intval($project_id);
        $domain = sanitize_text_field((string) $domain);
        $limit = max(1, min(500, intval($limit)));
        $force_refresh = !empty($force_refresh);

        if ($project_id <= 0) {
            return new WP_Error('invalid_project', 'Invalid project id');
        }
        if ($domain === '') {
            return new WP_Error('missing_domain', 'Target domain is required.');
        }

        if (!$force_refresh) {
            $cached_snapshot = get_option($this->aysa_get_backlink_audit_domain_key($project_id, $domain), array());
            $cached_items = (is_array($cached_snapshot) && isset($cached_snapshot['items']) && is_array($cached_snapshot['items']))
                ? $cached_snapshot['items']
                : array();
            if (!empty($cached_items)) {
                return array(
                    'items' => $cached_items,
                    'raw' => array(),
                    'provider' => (string) ($cached_snapshot['provider'] ?? 'local_cache'),
                    'generated_at' => (string) ($cached_snapshot['generated_at'] ?? ''),
                    'cached' => true,
                );
            }
        }

        $broker_result = $this->aysa_broker_request('POST', '/v1/dataforseo/backlink-audit', array(
            'target' => $domain,
            'domain' => $domain,
            'limit' => $limit,
            'project_id' => $project_id,
        ), 90);

        if (is_wp_error($broker_result)) {
            $debug_message = 'Backlink audit broker failed: ' . $broker_result->get_error_message();
            $message = 'The live backlink provider was unavailable, so the audit used the backup analysis flow.';
            if (defined('AYSA_DEBUG') && AYSA_DEBUG) {
                $message .= ' ' . $debug_message;
            }
            $fallback_link_issues = $this->aysa_fetch_adverlink_link_issues($project_id);
            if (is_wp_error($fallback_link_issues)) {
                return new WP_Error('backlink_audit_failed', $message);
            }

            $issues_items = (is_array($fallback_link_issues) && isset($fallback_link_issues['items']) && is_array($fallback_link_issues['items']))
                ? $fallback_link_issues['items']
                : array();
            $normalized_fallback = array();
            foreach ($issues_items as $it) {
                if (!is_array($it)) {
                    continue;
                }
                $normalized_fallback[] = array(
                    'source_url' => (string) ($it['published_on_url'] ?? ''),
                    'target_url' => (string) ($it['linked_to_url'] ?? ''),
                    'anchor_text' => (string) ($it['published_keyword'] ?? ''),
                    'referring_domain' => (string) ($it['portal_domain'] ?? ''),
                    'status' => (string) ($it['status'] ?? ''),
                    'health' => (string) ($it['status'] ?? ''),
                    'issue_type' => (string) ($it['issue_type'] ?? ''),
                    'issue_message' => (string) ($it['issue_summary'] ?? $it['issue_message'] ?? ''),
                    'first_seen' => (string) ($it['detected_at'] ?? ''),
                    'last_seen' => (string) ($it['detected_at'] ?? ''),
                    'risk' => 'high',
                );
            }

            $snapshot = array(
                'domain' => $domain,
                'provider' => 'link_issues_fallback',
                'generated_at' => current_time('mysql'),
                'count' => count($normalized_fallback),
                'items' => $normalized_fallback,
                'fallback_reason' => $message,
            );
            update_option($this->aysa_get_backlink_audit_key($project_id), $snapshot, false);
            update_option($this->aysa_get_backlink_audit_domain_key($project_id, $domain), $snapshot, false);
            update_option($this->aysa_get_backlink_audit_sync_key($project_id), array(
                'synced_at' => current_time('mysql'),
                'domain' => $domain,
                'count' => count($normalized_fallback),
                'provider' => 'link_issues_fallback',
            ), false);
            if ($force_refresh) {
                $this->aysa_wallet_track_action_usage(
                    $project_id,
                    'aysa_adverlink_backlink_audit',
                    'offpage_backlink_audit_' . md5($domain),
                    array(
                        'module' => 'offpage',
                        'domain' => $domain,
                        'fallback' => true,
                        'items_count' => count($normalized_fallback),
                        'force_refresh' => 1,
                    ),
                    9,
                    'wp_ajax/aysa_adverlink_backlink_audit'
                );
            }

            return array(
                'items' => $normalized_fallback,
                'raw' => array(),
                'fallback' => true,
                'fallback_source' => 'link_issues',
                'fallback_reason' => $message,
                'generated_at' => $snapshot['generated_at'],
            );
        }

        $items = (isset($broker_result['items']) && is_array($broker_result['items'])) ? $broker_result['items'] : array();
        $snapshot = array(
            'domain' => $domain,
            'provider' => (string) ($broker_result['provider'] ?? 'api.aysa.ai'),
            'generated_at' => current_time('mysql'),
            'count' => count($items),
            'items' => $items,
        );
        update_option($this->aysa_get_backlink_audit_key($project_id), $snapshot, false);
        update_option($this->aysa_get_backlink_audit_domain_key($project_id, $domain), $snapshot, false);
        update_option($this->aysa_get_backlink_audit_sync_key($project_id), array(
            'synced_at' => current_time('mysql'),
            'domain' => $domain,
            'count' => count($items),
            'provider' => (string) ($broker_result['provider'] ?? 'api.aysa.ai'),
        ), false);
        if ($force_refresh) {
            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_adverlink_backlink_audit',
                'offpage_backlink_audit_' . md5($domain),
                array(
                    'module' => 'offpage',
                    'domain' => $domain,
                    'fallback' => false,
                    'items_count' => count($items),
                    'force_refresh' => 1,
                ),
                9,
                'wp_ajax/aysa_adverlink_backlink_audit'
            );
        }

        return array(
            'items' => $items,
            'raw' => (isset($broker_result['raw']) && is_array($broker_result['raw'])) ? $broker_result['raw'] : array(),
            'provider' => (string) ($broker_result['provider'] ?? 'api.aysa.ai'),
            'generated_at' => $snapshot['generated_at'],
        );
    }

    public function handle_competitor_backlinks_gap_insights() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        $home_host = $this->aysa_monitoring_extract_host((string) home_url('/'));
        $resolved_context = $this->aysa_monitoring_resolve_context($home_host);
        $target = strtolower(trim((string) ($resolved_context['target'] ?? '')));
        if ($target !== '') {
            $target = $this->aysa_monitoring_extract_host($target);
        }
        if ($target === '') {
            $target = $home_host;
        }
        if ($target === '') {
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
            $target = $this->aysa_monitoring_extract_host((string) wp_unslash($_POST['target'] ?? ''));
        }
        if ($target === '') {
            wp_send_json_error('Target domain is required.');
            return;
        }

        $competitors = array();
        $seed_data = $this->aysa_keyword_strategy_extract_seed_data(get_current_user_id());
        if (isset($seed_data['competitors']) && is_array($seed_data['competitors'])) {
            $competitors = $seed_data['competitors'];
        }
        if (empty($competitors)) {
            $shared_user_id = intval(get_option('aysa_user_id', 0));
            if ($shared_user_id > 0) {
                $shared_seed_data = $this->aysa_keyword_strategy_extract_seed_data($shared_user_id);
                if (isset($shared_seed_data['competitors']) && is_array($shared_seed_data['competitors'])) {
                    $competitors = $shared_seed_data['competitors'];
                }
            }
        }

        $normalized_competitors = array();
        foreach ($competitors as $raw) {
            $host = $this->aysa_monitoring_extract_host((string) $raw);
            if ($host === '') {
                $host = strtolower(trim((string) $raw));
            }
            $host = preg_replace('/^www\./', '', $host);
            if ($host === '' || $host === $target) {
                continue;
            }
            if (!preg_match('/^[a-z0-9.-]+\.[a-z]{2,}$/', $host)) {
                continue;
            }
            $normalized_competitors[$host] = true;
        }
        $competitor_domains = array_keys($normalized_competitors);
        if (empty($competitor_domains)) {
            wp_send_json_error('No competitors configured in Setup SEO Profile.');
            return;
        }

        $limit = max(50, min(300, intval(wp_unslash($_POST['limit'] ?? 150))));
        $domains = array_merge(array($target), $competitor_domains);

        $force_refresh = !empty($_POST['force_refresh']) && intval(wp_unslash($_POST['force_refresh'])) === 1;

        $profiles = array();
        $errors = array();
        foreach ($domains as $domain) {
            $cached_domain = get_option($this->aysa_get_backlink_audit_domain_key($project_id, $domain), array());
            $cached_items = (is_array($cached_domain) && isset($cached_domain['items']) && is_array($cached_domain['items']))
                ? $cached_domain['items']
                : array();

            if (!$force_refresh) {
                $profiles[$domain] = !empty($cached_items)
                    ? $this->aysa_backlinks_build_profile($domain, $cached_items)
                    : $this->aysa_backlinks_empty_profile($domain);
                continue;
            }

            $broker_result = $this->aysa_broker_request('POST', '/v1/dataforseo/backlink-audit', array(
                'target' => $domain,
                'domain' => $domain,
                'limit' => $limit,
                'project_id' => $project_id,
            ), 180);
            if (is_wp_error($broker_result)) {
                $errors[] = array('domain' => $domain, 'error' => $broker_result->get_error_message());
                $profiles[$domain] = !empty($cached_items)
                    ? $this->aysa_backlinks_build_profile($domain, $cached_items)
                    : $this->aysa_backlinks_empty_profile($domain);
                continue;
            }

            $items = (isset($broker_result['items']) && is_array($broker_result['items'])) ? $broker_result['items'] : array();
            $snapshot = array(
                'domain' => $domain,
                'provider' => (string) ($broker_result['provider'] ?? 'dataforseo'),
                'generated_at' => current_time('mysql'),
                'count' => count($items),
                'items' => $items,
            );
            update_option($this->aysa_get_backlink_audit_domain_key($project_id, $domain), $snapshot, false);
            $profiles[$domain] = $this->aysa_backlinks_build_profile($domain, $items);
        }

        $target_profile = isset($profiles[$target]) ? $profiles[$target] : $this->aysa_backlinks_empty_profile($target);
        $competitor_profiles = array();
        foreach ($competitor_domains as $domain) {
            $competitor_profiles[] = isset($profiles[$domain]) ? $profiles[$domain] : $this->aysa_backlinks_empty_profile($domain);
        }

        $brand_terms = $this->aysa_backlinks_extract_brand_terms($target, get_current_user_id());
        $benchmark = $this->aysa_backlinks_build_benchmark($competitor_profiles, $brand_terms);
        $gap = $this->aysa_backlinks_build_gap($target_profile, $benchmark, $brand_terms);
        update_option('aysa_backlink_summary_' . intval($project_id), array(
            'generated_at' => current_time('mysql'),
            'items' => array_values($profiles),
            'target_profile' => $target_profile,
            'competitor_profiles' => $competitor_profiles,
            'benchmark' => $benchmark,
            'gap' => $gap,
            'brand_terms' => $brand_terms,
            'errors' => $errors,
        ), false);

        // Local cached reads should not consume credits.
        if ($force_refresh) {
            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_competitor_backlinks_gap_insights',
                'competitor_backlinks_gap_insights',
                array(
                    'module' => 'competitor_backlinks',
                    'target' => $target,
                    'competitors_count' => count($competitors),
                    'errors_count' => count($errors),
                    'force_refresh' => 1,
                ),
                45,
                '/v1/backlinks/gap-insights'
            );
        }

        wp_send_json_success(array(
            'target' => $target,
            'project_id' => $project_id,
            'profiles' => $profiles,
            'target_profile' => $target_profile,
            'competitor_profiles' => $competitor_profiles,
            'benchmark' => $benchmark,
            'gap' => $gap,
            'brand_terms' => $brand_terms,
            'errors' => $errors,
            'generated_at' => current_time('mysql'),
        ));
    }

    public function handle_competitor_backlinks_get_cached() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $domains_raw = isset($_POST['domains']) ? wp_unslash($_POST['domains']) : '[]';
        $domains = json_decode((string) $domains_raw, true);
        if (!is_array($domains)) {
            $domains = array();
        }

        $result = array();
        foreach ($domains as $raw) {
            $domain = $this->aysa_monitoring_extract_host((string) $raw);
            if ($domain === '') {
                $domain = strtolower(trim((string) $raw));
            }
            $domain = preg_replace('/^www\./', '', $domain);
            if ($domain === '' || !preg_match('/^[a-z0-9.-]+\.[a-z]{2,}$/', $domain)) {
                continue;
            }

            $snapshot = get_option($this->aysa_get_backlink_audit_domain_key($project_id, $domain), array());
            $items = (is_array($snapshot) && isset($snapshot['items']) && is_array($snapshot['items']))
                ? $snapshot['items']
                : array();

            $result[$domain] = array(
                'items' => $items,
                'count' => count($items),
                'generated_at' => is_array($snapshot) ? (string) ($snapshot['generated_at'] ?? '') : '',
                'provider' => is_array($snapshot) ? (string) ($snapshot['provider'] ?? '') : '',
            );
        }

        wp_send_json_success(array(
            'project_id' => $project_id,
            'domains' => $result,
        ));
    }

    private function aysa_backlinks_empty_profile($domain) {
        return array(
            'domain' => (string) $domain,
            'total_backlinks' => 0,
            'ref_domains' => 0,
            'dofollow_count' => 0,
            'dofollow_ratio' => 0,
            'avg_authority' => 0,
            'high_authority_links' => 0,
            'top_anchors' => array(),
            'keyword_cloud' => array(),
        );
    }

    private function aysa_backlinks_build_profile($domain, $items) {
        $total = is_array($items) ? count($items) : 0;
        $ref_domains = array();
        $anchor_counts = array();
        $term_counts = array();
        $dofollow = 0;
        $authority_sum = 0.0;
        $authority_count = 0;
        $high_authority = 0;

        if (is_array($items)) {
            foreach ($items as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $ref = strtolower(trim((string) ($row['referring_domain'] ?? $row['source_domain'] ?? $row['domain_from'] ?? '')));
                if ($ref !== '') {
                    $ref_domains[$ref] = true;
                }

                $anchor = trim((string) ($row['anchor_text'] ?? $row['anchor'] ?? ''));
                if ($anchor !== '') {
                    $key = mb_strtolower($anchor, 'UTF-8');
                    $anchor_counts[$key] = intval($anchor_counts[$key] ?? 0) + 1;
                    $terms = preg_split('/[^\p{L}\p{N}]+/u', $key);
                    if (is_array($terms)) {
                        foreach ($terms as $term) {
                            $term = trim((string) $term);
                            if (mb_strlen($term, 'UTF-8') < 3) {
                                continue;
                            }
                            if (in_array($term, array('the', 'and', 'for', 'cu', 'din', 'care', 'sunt', 'sau', 'www', 'com', 'ro'), true)) {
                                continue;
                            }
                            $term_counts[$term] = intval($term_counts[$term] ?? 0) + 1;
                        }
                    }
                }

                $is_dofollow = !empty($row['dofollow']);
                if ($is_dofollow) {
                    $dofollow++;
                }

                $authority = intval($row['domain_rank'] ?? 0);
                if ($authority > 0) {
                    $authority_sum += $authority;
                    $authority_count++;
                    if ($authority >= 50) {
                        $high_authority++;
                    }
                }
            }
        }

        arsort($anchor_counts);
        arsort($term_counts);

        $top_anchors = array();
        foreach (array_slice($anchor_counts, 0, 15, true) as $anchor => $count) {
            $top_anchors[] = array('anchor' => $anchor, 'count' => intval($count));
        }
        $keyword_cloud = array();
        foreach (array_slice($term_counts, 0, 20, true) as $term => $count) {
            $keyword_cloud[] = array('term' => $term, 'count' => intval($count));
        }

        return array(
            'domain' => (string) $domain,
            'total_backlinks' => $total,
            'ref_domains' => count($ref_domains),
            'dofollow_count' => $dofollow,
            'dofollow_ratio' => $total > 0 ? round(($dofollow / $total) * 100, 1) : 0,
            'avg_authority' => $authority_count > 0 ? round($authority_sum / $authority_count, 1) : 0,
            'high_authority_links' => $high_authority,
            'top_anchors' => $top_anchors,
            'keyword_cloud' => $keyword_cloud,
        );
    }

    private function aysa_backlinks_build_benchmark($competitor_profiles, $brand_terms = array()) {
        $count = is_array($competitor_profiles) ? count($competitor_profiles) : 0;
        if ($count === 0) {
            return array(
                'ref_domains' => 0,
                'total_backlinks' => 0,
                'avg_authority' => 0,
                'dofollow_ratio' => 0,
                'high_authority_links' => 0,
                'keyword_cloud' => array(),
                'top_anchors' => array(),
            );
        }

        $sum_ref = 0;
        $sum_total = 0;
        $sum_auth = 0.0;
        $sum_dofollow_ratio = 0.0;
        $sum_high = 0;
        $anchor_counts = array();
        $term_counts = array();

        foreach ($competitor_profiles as $p) {
            if (!is_array($p)) {
                continue;
            }
            $sum_ref += intval($p['ref_domains'] ?? 0);
            $sum_total += intval($p['total_backlinks'] ?? 0);
            $sum_auth += floatval($p['avg_authority'] ?? 0);
            $sum_dofollow_ratio += floatval($p['dofollow_ratio'] ?? 0);
            $sum_high += intval($p['high_authority_links'] ?? 0);

            $anchors = isset($p['top_anchors']) && is_array($p['top_anchors']) ? $p['top_anchors'] : array();
            foreach ($anchors as $a) {
                if (!is_array($a)) {
                    continue;
                }
                $k = trim((string) ($a['anchor'] ?? ''));
                if ($k === '') {
                    continue;
                }
                if ($this->aysa_backlinks_text_has_bwp_rand($k, $brand_terms)) {
                    continue;
                }
                $anchor_counts[$k] = intval($anchor_counts[$k] ?? 0) + intval($a['count'] ?? 0);
            }
            $cloud = isset($p['keyword_cloud']) && is_array($p['keyword_cloud']) ? $p['keyword_cloud'] : array();
            foreach ($cloud as $kw) {
                if (!is_array($kw)) {
                    continue;
                }
                $k = trim((string) ($kw['term'] ?? ''));
                if ($k === '') {
                    continue;
                }
                if ($this->aysa_backlinks_text_has_bwp_rand($k, $brand_terms)) {
                    continue;
                }
                $term_counts[$k] = intval($term_counts[$k] ?? 0) + intval($kw['count'] ?? 0);
            }
        }

        arsort($anchor_counts);
        arsort($term_counts);
        $top_anchors = array();
        foreach (array_slice($anchor_counts, 0, 15, true) as $anchor => $cnt) {
            $top_anchors[] = array('anchor' => $anchor, 'count' => intval($cnt));
        }
        $keyword_cloud = array();
        foreach (array_slice($term_counts, 0, 20, true) as $term => $cnt) {
            $keyword_cloud[] = array('term' => $term, 'count' => intval($cnt));
        }

        return array(
            'ref_domains' => intval(round($sum_ref / max(1, $count))),
            'total_backlinks' => intval(round($sum_total / max(1, $count))),
            'avg_authority' => round($sum_auth / max(1, $count), 1),
            'dofollow_ratio' => round($sum_dofollow_ratio / max(1, $count), 1),
            'high_authority_links' => intval(round($sum_high / max(1, $count))),
            'top_anchors' => $top_anchors,
            'keyword_cloud' => $keyword_cloud,
        );
    }

    private function aysa_backlinks_build_gap($target_profile, $benchmark, $brand_terms = array()) {
        $target_ref = intval($target_profile['ref_domains'] ?? 0);
        $target_total = intval($target_profile['total_backlinks'] ?? 0);
        $target_high = intval($target_profile['high_authority_links'] ?? 0);
        $target_anchor_set = array();
        if (isset($target_profile['top_anchors']) && is_array($target_profile['top_anchors'])) {
            foreach ($target_profile['top_anchors'] as $a) {
                if (is_array($a) && !empty($a['anchor'])) {
                    $target_anchor_set[(string) $a['anchor']] = true;
                }
            }
        }

        $bench_ref = intval($benchmark['ref_domains'] ?? 0);
        $bench_total = intval($benchmark['total_backlinks'] ?? 0);
        $bench_high = intval($benchmark['high_authority_links'] ?? 0);

        $need_ref = max(0, $bench_ref - $target_ref);
        $need_total = max(0, $bench_total - $target_total);
        $need_high = max(0, $bench_high - $target_high);

        $missing_anchors = array();
        $bench_anchors = isset($benchmark['top_anchors']) && is_array($benchmark['top_anchors']) ? $benchmark['top_anchors'] : array();
        foreach ($bench_anchors as $a) {
            if (!is_array($a) || empty($a['anchor'])) {
                continue;
            }
            $anchor = (string) $a['anchor'];
            if ($this->aysa_backlinks_text_has_bwp_rand($anchor, $brand_terms)) {
                continue;
            }
            if (!isset($target_anchor_set[$anchor])) {
                $missing_anchors[] = $a;
            }
            if (count($missing_anchors) >= 12) {
                break;
            }
        }
        $recommended_anchor_cloud = $this->aysa_backlinks_build_recommended_anchor_cloud(
            isset($benchmark['keyword_cloud']) && is_array($benchmark['keyword_cloud']) ? $benchmark['keyword_cloud'] : array(),
            $need_ref,
            $brand_terms
        );

        return array(
            'needed_ref_domains' => $need_ref,
            'needed_total_backlinks' => $need_total,
            'needed_high_authority_links' => $need_high,
            'target_dofollow_ratio' => floatval($benchmark['dofollow_ratio'] ?? 0),
            'plan_90_days' => array(
                'high_authority' => intval(ceil($need_ref * 0.25)),
                'medium_authority' => intval(ceil($need_ref * 0.45)),
                'baseline_authority' => intval(max(0, $need_ref - ceil($need_ref * 0.25) - ceil($need_ref * 0.45))),
            ),
            'missing_anchor_opportunities' => $missing_anchors,
            'recommended_anchor_cloud' => $recommended_anchor_cloud,
            'conclusion' => sprintf(
                'To match competitor benchmark, target +%d referring domains (+%d high-authority links) and +%d backlinks, while moving dofollow ratio toward %.1f%%.',
                $need_ref,
                $need_high,
                $need_total,
                floatval($benchmark['dofollow_ratio'] ?? 0)
            ),
        );
    }

    private function aysa_backlinks_extract_brand_terms($target_domain, $user_id) {
        $terms = array();
        $target_domain = strtolower(trim((string) $target_domain));
        if ($target_domain !== '') {
            $parts = preg_split('/[^a-z0-9]+/i', $target_domain);
            if (is_array($parts)) {
                foreach ($parts as $part) {
                    $part = strtolower(trim((string) $part));
                    if ($part !== '' && strlen($part) >= 3) {
                        $terms[$part] = true;
                    }
                }
            }
        }

        $candidate_users = array(intval($user_id));
        $shared_user_id = intval(get_option('aysa_user_id', 0));
        if ($shared_user_id > 0 && !in_array($shared_user_id, $candidate_users, true)) {
            $candidate_users[] = $shared_user_id;
        }
        foreach ($candidate_users as $uid) {
            $general = get_option('aysa_wizard_general_' . intval($uid), array());
            if (!is_array($general)) {
                continue;
            }
            $raw_values = array(
                (string) ($general['brand'] ?? ''),
                (string) ($general['company_name'] ?? ''),
                (string) ($general['website'] ?? ''),
            );
            foreach ($raw_values as $raw) {
                $raw = strtolower(trim($raw));
                if ($raw === '') {
                    continue;
                }
                $parts = preg_split('/[^\p{L}\p{N}]+/u', $raw);
                if (!is_array($parts)) {
                    continue;
                }
                foreach ($parts as $p) {
                    $p = strtolower(trim((string) $p));
                    if ($p !== '' && mb_strlen($p, 'UTF-8') >= 3) {
                        $terms[$p] = true;
                    }
                }
            }
        }

        return array_keys($terms);
    }

    private function aysa_backlinks_text_has_bwp_rand($text, $brand_terms) {
        $normalized = mb_strtolower(trim((string) $text), 'UTF-8');
        if ($normalized === '') {
            return false;
        }
        if (!is_array($brand_terms) || empty($brand_terms)) {
            return false;
        }
        foreach ($brand_terms as $term) {
            $term = mb_strtolower(trim((string) $term), 'UTF-8');
            if ($term === '' || mb_strlen($term, 'UTF-8') < 3) {
                continue;
            }
            if (mb_strpos($normalized, $term, 0, 'UTF-8') !== false) {
                return true;
            }
        }
        return false;
    }

    private function aysa_backlinks_build_recommended_anchor_cloud($keyword_cloud, $needed_links, $brand_terms = array()) {
        $needed = max(0, intval($needed_links));
        if ($needed === 0 || !is_array($keyword_cloud) || empty($keyword_cloud)) {
            return array();
        }

        $pool = array();
        foreach ($keyword_cloud as $item) {
            if (!is_array($item)) {
                continue;
            }
            $term = trim((string) ($item['term'] ?? ''));
            if ($term === '' || $this->aysa_backlinks_text_has_bwp_rand($term, $brand_terms)) {
                continue;
            }
            $count = max(1, intval($item['count'] ?? 0));
            $pool[] = array('term' => $term, 'count' => $count);
            if (count($pool) >= 12) {
                break;
            }
        }
        if (empty($pool)) {
            return array();
        }

        $total_weight = 0;
        foreach ($pool as $p) {
            $total_weight += intval($p['count']);
        }
        if ($total_weight <= 0) {
            $total_weight = count($pool);
        }

        $recommended = array();
        $allocated = 0;
        foreach ($pool as $idx => $p) {
            $share = intval(round(($p['count'] / $total_weight) * $needed));
            if ($share <= 0) {
                $share = 1;
            }
            $recommended[$idx] = array(
                'term' => $p['term'],
                'weight' => intval($p['count']),
                'needed_links' => $share,
            );
            $allocated += $share;
        }

        while ($allocated > $needed) {
            foreach ($recommended as $idx => $item) {
                if ($allocated <= $needed) {
                    break;
                }
                if (intval($item['needed_links']) > 1) {
                    $recommended[$idx]['needed_links'] = intval($item['needed_links']) - 1;
                    $allocated--;
                }
            }
            if ($needed === 0) {
                break;
            }
        }
        while ($allocated < $needed) {
            $recommended[0]['needed_links'] = intval($recommended[0]['needed_links']) + 1;
            $allocated++;
        }

        return array_values($recommended);
    }

    public function handle_monitoring_rank_plan() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $context = $this->aysa_monitoring_resolve_context((string) wp_unslash($_POST['target'] ?? ''));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $associations_raw = isset($_POST['associations']) ? wp_unslash($_POST['associations']) : '';
        $associations = json_decode((string) $associations_raw, true);
        if (!is_array($associations)) {
            $associations = array();
        }

        $payload = array(
            'project_id' => $project_id,
            'target' => $context['target'],
            'se_domain' => $context['se_domain'],
            'location_code' => $context['location_code'],
            'language_code' => $context['language_code'],
            'associations' => $associations,
        );
        $result = $this->aysa_broker_request('POST', '/v1/dataforseo/rank-tracking/plan-enriched', $payload, 60);
        if (is_wp_error($result)) {
            wp_send_json_error('Rank plan failed: ' . $result->get_error_message());
            return;
        }
        if (is_array($result) && isset($result['rows']) && is_array($result['rows'])) {
            $persist = $this->aysa_monitoring_persist_rows($project_id, $context['target'], $result['rows']);
            $result['persist_debug'] = $persist;
        }
        wp_send_json_success($result);
    }

    private function aysa_monitoring_extract_overview_rows($result) {
        if (!is_array($result)) {
            return array();
        }
        if (isset($result['rows']) && is_array($result['rows'])) {
            return $result['rows'];
        }
        if (isset($result['results']) && is_array($result['results'])) {
            return $result['results'];
        }
        if (isset($result['data']) && is_array($result['data'])) {
            if (isset($result['data']['rows']) && is_array($result['data']['rows'])) {
                return $result['data']['rows'];
            }
            if (isset($result['data']['results']) && is_array($result['data']['results'])) {
                return $result['data']['results'];
            }
            if (array_keys($result['data']) === range(0, count($result['data']) - 1)) {
                return $result['data'];
            }
        }
        return array();
    }

    private function aysa_adjustments_create_wallet_topup_order($project_id, $credits_cents, $language, $context = array()) {
        $project_id = intval($project_id);
        $credits_cents = intval($credits_cents);
        $context = is_array($context) ? $context : array();

        if ($project_id <= 0 || $credits_cents < 500) {
            return array(
                'module' => 'billing_wallet',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I need a valid top-up package before I can generate the payment link.',
                    'Am nevoie de un pachet valid de top-up înainte să pot genera linkul de plată.',
                    'Нужен ми е валиден top-up пакет, преди да мога да генерирам линк за плащане.'
                ),
            );
        }

        if (!class_exists('Aysa_Wallet_Manager')) {
            $wallet_file = AYSA_PLUGIN_DIR . 'includes/class-aysa-wallet-manager.php';
            if (file_exists($wallet_file)) {
                require_once $wallet_file;
            }
        }

        if (!class_exists('Aysa_Wallet_Manager')) {
            return array(
                'module' => 'billing_wallet',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'The wallet manager is not available right now.',
                    'Managerul wallet-ului nu este disponibil acum.',
                    'Wallet manager-ът не е наличен в момента.'
                ),
            );
        }

        $wallet_manager = Aysa_Wallet_Manager::get_instance();
        $context = array_merge(array(
            'module' => 'billing_wallet',
            'action_key' => 'wallet_topup',
            'stage_key' => 'plugin_payment_cta',
            'requested_credits' => $credits_cents,
            'source' => 'agent8_payment_cta',
            'order_type' => 'topup',
            'context' => 'agent8_payment_recovery',
            'invoice_label' => 'Agent8 wallet top-up',
        ), $context);
        $order = $wallet_manager->create_topup_order($project_id, $credits_cents, get_current_user_id(), $context);
        if (is_wp_error($order)) {
            $message = $order->get_error_message();
            if ($message === 'billing_company_missing') {
                $message = $this->aysa_adjustments_tr(
                    $language,
                    'I cannot generate the payment link yet because billing company details are missing in AYSA.',
                    'Nu pot genera încă linkul de plată pentru că lipsesc datele companiei de facturare din AYSA.',
                    'Не мога още да генерирам линк за плащане, защото липсват данните за фактуриране в AYSA.'
                );
            }
            return array(
                'module' => 'billing_wallet',
                'assistant_message' => $message,
            );
        }

        $payment_link = is_array($order) ? trim((string) ($order['payment_link'] ?? '')) : '';
        $amount_eur = number_format($credits_cents / 100, 2, '.', '');

        return array(
            'module' => 'billing_wallet',
            'assistant_message' => $this->aysa_adjustments_tr(
                $language,
                'I generated the Stripe payment link for the ' . $amount_eur . ' EUR top-up. I am opening it now, and after payment the credits will be added to your AYSA wallet automatically.',
                'Am generat linkul Stripe de plată pentru top-up-ul de ' . $amount_eur . ' EUR. Îl deschid acum, iar după plată creditele intră automat în wallet-ul tău AYSA.',
                'Генерирах Stripe линк за плащане за top-up от ' . $amount_eur . ' EUR. Отварям го сега и след плащане кредитите ще влязат автоматично в AYSA wallet-а ти.'
            ),
            'payment_link' => $payment_link,
        );
    }

    private function aysa_adjustments_create_wallet_supplement_order($project_id, $credits_cents, $language, $context = 'order', $module = 'billing_wallet', $meta = array()) {
        $project_id = intval($project_id);
        $credits_cents = intval($credits_cents);
        $meta = is_array($meta) ? $meta : array();

        if ($project_id <= 0 || $credits_cents < 1) {
            return array(
                'module' => $module,
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I need a valid supplemental amount before I can generate the payment link.',
                    'Am nevoie de o sumă suplimentară validă înainte să pot genera linkul de plată.',
                    'Нужна ми е валидна допълнителна сума, преди да мога да генерирам линк за плащане.'
                ),
            );
        }

        if (!class_exists('Aysa_Wallet_Manager')) {
            $wallet_file = AYSA_PLUGIN_DIR . 'includes/class-aysa-wallet-manager.php';
            if (file_exists($wallet_file)) {
                require_once $wallet_file;
            }
        }

        if (!class_exists('Aysa_Wallet_Manager')) {
            return array(
                'module' => $module,
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'The wallet manager is not available right now.',
                    'Managerul wallet-ului nu este disponibil acum.',
                    'Wallet manager-ът не е наличен в момента.'
                ),
            );
        }

        $service_type = match ($context) {
            'seo_article_order' => 'seo_article',
            'professional_article_order' => 'professional_article',
            'link_insertion_order' => 'link_insertion',
            'seo_text_ads_order' => 'seo_ads_text',
            default => '',
        };
        $portal_domain = sanitize_text_field((string) ($meta['portal_domain'] ?? ''));
        $invoice_label = match ($context) {
            'seo_article_order' => 'SEO article',
            'professional_article_order' => 'Professional article',
            'link_insertion_order' => 'Link insertion',
            'seo_text_ads_order' => 'SEO Text Ads',
            default => 'Off-page supplement',
        };
        if ($portal_domain !== '') {
            $invoice_label .= ' - ' . $portal_domain;
        }

        $wallet_manager = Aysa_Wallet_Manager::get_instance();
        $target_url = sanitize_text_field((string) ($meta['target_url'] ?? ''));
        $recommendation_id = sanitize_text_field((string) ($meta['recommendation_id'] ?? ''));
        $paused_ref_parts = array_filter(array(
            sanitize_key((string) $context),
            $portal_domain,
            $recommendation_id,
            $target_url,
        ));
        $order = $wallet_manager->create_supplement_order($project_id, $credits_cents, get_current_user_id(), array(
            'module' => sanitize_key((string) $module),
            'action_key' => 'offpage_supplement_topup',
            'stage_key' => sanitize_key((string) $context),
            'paused_action_ref' => implode('|', $paused_ref_parts),
            'requested_credits' => $credits_cents,
            'service_type' => $service_type,
            'portal_id' => sanitize_text_field((string) ($meta['portal_id'] ?? '')),
            'portal_domain' => $portal_domain,
            'context' => $context,
            'invoice_label' => $invoice_label,
        ));
        if (is_wp_error($order)) {
            $message = $order->get_error_message();
            if ($message === 'billing_company_missing') {
                $message = $this->aysa_adjustments_tr(
                    $language,
                    'I cannot generate the payment link yet because billing company details are missing in AYSA.',
                    'Nu pot genera încă linkul de plată pentru că lipsesc datele companiei de facturare din AYSA.',
                    'Не мога още да генерирам линк за плащане, защото липсват данните за фактуриране в AYSA.'
                );
            }
            return array(
                'module' => $module,
                'assistant_message' => $message,
            );
        }

        $payment_link = is_array($order) ? trim((string) ($order['payment_link'] ?? '')) : '';
        $amount_eur = number_format($credits_cents / 100, 2, '.', '');

        return array(
            'module' => $module,
            'assistant_message' => $this->aysa_adjustments_tr(
                $language,
                'I generated the Stripe payment link for the missing amount of ' . $amount_eur . ' EUR. I am opening it now, and after payment you can continue the off-page order.',
                'Am generat linkul Stripe de plată pentru diferența de ' . $amount_eur . ' EUR. Îl deschid acum, iar după plată poți continua comanda off-page.',
                'Генерирах Stripe линк за плащане за липсващата сума от ' . $amount_eur . ' EUR. Отварям го сега и след плащане можеш да продължиш off-page поръчката.'
            ),
            'payment_link' => $payment_link,
            'order_type' => 'offpage_supplement',
        );
    }

    private function aysa_build_insufficient_credits_payment_response($project_id, $required_credits, $available_credits, $language, $context = 'order', $module = 'billing_wallet', $meta = array()) {
        $project_id = intval($project_id);
        $required_credits = max(0, intval($required_credits));
        $available_credits = max(0, intval($available_credits));
        $missing_credits = max(0, $required_credits - $available_credits);
        $meta = is_array($meta) ? $meta : array();

        if ($project_id <= 0 || $missing_credits <= 0) {
            return array(
                'module' => $module,
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I could not calculate the missing credits for this payment.',
                    'Nu am putut calcula creditele lipsă pentru această plată.',
                    'Не успях да изчисля липсващите кредити за това плащане.'
                ),
            );
        }

        $topup_contexts = array('seo_article_order', 'professional_article_order', 'link_insertion_order', 'seo_text_ads_order');
        if (in_array((string) $context, $topup_contexts, true)) {
            $topup = $this->aysa_adjustments_create_wallet_supplement_order($project_id, $missing_credits, $language, $context, $module, $meta);
        } else {
            $topup = $this->aysa_adjustments_create_wallet_topup_order($project_id, $missing_credits, $language, array(
                'module' => sanitize_key((string) $module),
                'action_key' => 'insufficient_credits_topup',
                'stage_key' => sanitize_key((string) $context),
                'paused_action_ref' => sanitize_text_field((string) ($meta['action_ref'] ?? ($meta['run_id'] ?? $context))),
                'run_id' => sanitize_text_field((string) ($meta['run_id'] ?? '')),
                'requested_credits' => $missing_credits,
            ));
        }
        $payment_link = trim((string) ($topup['payment_link'] ?? ''));
        $amount_eur = number_format($missing_credits / 100, 2, '.', '');

        $purpose_text = match ($context) {
            'seo_article_order' => $this->aysa_adjustments_tr($language, 'SEO article order', 'comanda de articol SEO', 'поръчката за SEO статия'),
            'professional_article_order' => $this->aysa_adjustments_tr($language, 'professional article order', 'comanda de articol profesional', 'поръчката за професионална статия'),
            'link_insertion_order' => $this->aysa_adjustments_tr($language, 'link insertion order', 'comanda de inserare link', 'поръчката за link insertion'),
            'seo_text_ads_order' => $this->aysa_adjustments_tr($language, 'SEO Text Ads order', 'comanda SEO Text Ads', 'поръчката за SEO Text Ads'),
            default => $this->aysa_adjustments_tr($language, 'order', 'comandă', 'поръчка'),
        };

        if ($payment_link === '') {
            return array(
                'module' => $module,
                'assistant_message' => trim((string) ($topup['assistant_message'] ?? '')),
                'required_credits' => $required_credits,
                'available_credits' => $available_credits,
                'missing_credits' => $missing_credits,
            );
        }

        return array(
            'module' => $module,
            'assistant_message' => match ($language) {
                'ro' => 'Nu ai suficiente credite pentru ' . $purpose_text . '. Îți lipsesc ' . $missing_credits . ' credite (' . $amount_eur . ' EUR). Am generat linkul Stripe de plată. După plată, poți relua comanda din interfață.',
                'bg' => 'Нямаш достатъчно кредити за ' . $purpose_text . '. Липсват ти ' . $missing_credits . ' кредита (' . $amount_eur . ' EUR). Генерирах Stripe линка за плащане. След плащането можеш да повториш поръчката от интерфейса.',
                default => 'You do not have enough credits for this ' . $purpose_text . '. You are missing ' . $missing_credits . ' credits (' . $amount_eur . ' EUR). I generated the Stripe payment link. After payment, you can retry the order from the interface.',
            },
            'payment_link' => $payment_link,
            'required_credits' => $required_credits,
            'available_credits' => $available_credits,
            'missing_credits' => $missing_credits,
            'order_type' => trim((string) ($topup['order_type'] ?? 'topup')),
        );
    }

    private function aysa_get_wallet_manager_instance() {
        if (!class_exists('Aysa_Wallet_Manager')) {
            $wallet_file = AYSA_PLUGIN_DIR . 'includes/class-aysa-wallet-manager.php';
            if (file_exists($wallet_file)) {
                require_once $wallet_file;
            }
        }

        if (!class_exists('Aysa_Wallet_Manager')) {
            return null;
        }

        return Aysa_Wallet_Manager::get_instance();
    }

    private function aysa_build_agent_wallet_snapshot($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array();
        }

        $wallet_snapshot = array();
        $wallet_manager = $this->aysa_get_wallet_manager_instance();
        if ($wallet_manager && method_exists($wallet_manager, 'get_wallet_snapshot')) {
            $wallet_snapshot = $wallet_manager->get_wallet_snapshot($project_id, array(
                'allow_remote' => true,
                'force_remote' => true,
            ));
            $resolved_project_id = $this->aysa_resolve_remote_wallet_project_id($project_id);
            if ($resolved_project_id > 0 && $resolved_project_id !== $project_id) {
                $resolved_snapshot = $wallet_manager->get_wallet_snapshot($resolved_project_id, array(
                    'allow_remote' => true,
                    'force_remote' => true,
                ));
                if (is_array($resolved_snapshot) && !empty($resolved_snapshot)) {
                    $wallet_snapshot = $resolved_snapshot;
                }
            }
        }

        $actual_credits = intval($wallet_snapshot['balance_cents'] ?? 0);
        $reserved_credits = intval($wallet_snapshot['reserved_links_cents'] ?? 0);
        $available_credits = intval($wallet_snapshot['spendable_cents'] ?? max(0, $actual_credits - $reserved_credits));

        return array(
            'actual_credits' => intval($actual_credits),
            'reserved_credits' => intval($reserved_credits),
            'available_credits' => intval($available_credits),
            'balance_cents' => intval($actual_credits),
            'spendable_cents' => intval($available_credits),
            'reserved_links_cents' => intval($reserved_credits),
            'project_id' => intval($wallet_snapshot['project_id'] ?? $project_id),
            'sot_source' => sanitize_key((string) ($wallet_snapshot['sot_source'] ?? '')),
            'latest_usage_amount_cents' => intval($wallet_snapshot['latest_usage_amount_cents'] ?? 0),
            'latest_usage_action_key' => sanitize_key((string) ($wallet_snapshot['latest_usage_action_key'] ?? '')),
            'latest_usage_reference' => sanitize_text_field((string) ($wallet_snapshot['latest_usage_reference'] ?? '')),
            'latest_usage_at' => sanitize_text_field((string) ($wallet_snapshot['latest_usage_at'] ?? '')),
        );
    }

    private function aysa_resolve_agent8_canonical_project_id($project_id, $site_host = '', $key_validation = array()) {
        $project_id = intval($project_id);
        $key_validation = is_array($key_validation) ? $key_validation : array();

        $candidate = intval($key_validation['canonical_project_id'] ?? 0);
        if ($candidate > 0) {
            return $candidate;
        }

        $candidate = intval($key_validation['project_id'] ?? 0);
        if ($candidate > 0) {
            return $candidate;
        }

        $candidate = intval($key_validation['website_id'] ?? 0);
        if ($candidate > 0) {
            return $candidate;
        }

        $site_host = strtolower(trim((string) preg_replace('/^www\./i', '', (string) $site_host)));
        if ($site_host === '') {
            $site_host = (string) wp_parse_url(home_url('/'), PHP_URL_HOST);
            $site_host = strtolower(trim((string) preg_replace('/^www\./i', '', (string) $site_host)));
        }

        if ($site_host !== '' && !$this->aysa_setup_is_local_host($site_host)) {
            if (!class_exists('Aysa_API_Config')) {
                require_once AYSA_PLUGIN_DIR . 'includes/config/aysa-api-config.php';
            }

            $endpoint = '';
            if (class_exists('Aysa_API_Config') && method_exists('Aysa_API_Config', 'get_endpoint_url')) {
                $endpoint = (string) Aysa_API_Config::get_endpoint_url('project_by_domain');
            }

            if ($endpoint !== '') {
                $url = rtrim($endpoint, '/') . '/' . rawurlencode($site_host);
                $response = wp_remote_get($url, array(
                    'timeout' => 10,
                    'redirection' => 2,
                ));
                if (!is_wp_error($response)) {
                    $code = intval(wp_remote_retrieve_response_code($response));
                    if ($code >= 200 && $code < 300) {
                        $decoded = json_decode((string) wp_remote_retrieve_body($response), true);
                        if (is_array($decoded)) {
                            $data = isset($decoded['data']) && is_array($decoded['data']) ? $decoded['data'] : $decoded;
                            $candidate = intval($data['project_id'] ?? $data['projectId'] ?? 0);
                            if ($candidate > 0) {
                                return $candidate;
                            }
                            $candidate = intval($data['website_id'] ?? $data['websiteId'] ?? 0);
                            if ($candidate > 0) {
                                return $candidate;
                            }
                        }
                    }
                }
            }
        }

        return $project_id;
    }

    private function aysa_resolve_remote_wallet_project_id($project_id) {
        $project_id = intval($project_id);
        $host = '';

        if (class_exists('Aysa_Auth_Manager')) {
            $auth_status = Aysa_Auth_Manager::get_instance()->is_authenticated();
            $host = trim((string) ($auth_status['domain'] ?? ''));
        }

        if ($host === '') {
            $host = (string) wp_parse_url(home_url('/'), PHP_URL_HOST);
        }

        $host = strtolower(trim((string) preg_replace('/^www\./i', '', $host)));
        if ($host === '' || $this->aysa_setup_is_local_host($host)) {
            return $project_id;
        }

        if (!class_exists('Aysa_API_Config')) {
            require_once AYSA_PLUGIN_DIR . 'includes/config/aysa-api-config.php';
        }

        $endpoint = '';
        if (class_exists('Aysa_API_Config') && method_exists('Aysa_API_Config', 'get_endpoint_url')) {
            $endpoint = (string) Aysa_API_Config::get_endpoint_url('project_by_domain');
        }
        if ($endpoint === '') {
            return $project_id;
        }

        $url = rtrim($endpoint, '/') . '/' . rawurlencode($host);
        $response = wp_remote_get($url, array(
            'timeout' => 10,
            'redirection' => 2,
        ));
        if (is_wp_error($response)) {
            return $project_id;
        }

        $code = intval(wp_remote_retrieve_response_code($response));
        if ($code < 200 || $code >= 300) {
            return $project_id;
        }

        $decoded = json_decode((string) wp_remote_retrieve_body($response), true);
        if (!is_array($decoded)) {
            return $project_id;
        }

        $data = isset($decoded['data']) && is_array($decoded['data']) ? $decoded['data'] : $decoded;
        $remote_project_id = intval($data['project_id'] ?? $data['projectId'] ?? 0);
        if ($remote_project_id <= 0) {
            $remote_project_id = intval($data['website_id'] ?? $data['websiteId'] ?? 0);
        }

        return $remote_project_id > 0 ? $remote_project_id : $project_id;
    }

    private function aysa_is_included_link_eligible_service($service_type) {
        return in_array((string) $service_type, array('seo_article', 'professional_article', 'link_insertion', 'seo_ads_text'), true);
    }

    private function aysa_monitoring_extract_assoc_for_keyword($associations, $keyword) {
        $keyword = strtolower(trim((string) $keyword));
        if ($keyword === '' || !is_array($associations)) {
            return array();
        }
        foreach ($associations as $assoc) {
            if (!is_array($assoc)) {
                continue;
            }
            $kw = strtolower(trim((string) ($assoc['keyword'] ?? '')));
            if ($kw === $keyword) {
                return $assoc;
            }
        }
        return array();
    }

    private function aysa_monitoring_extract_overview_string($row, $keys) {
        if (!is_array($row)) {
            return '';
        }
        foreach ($keys as $key) {
            if (!array_key_exists($key, $row)) {
                continue;
            }
            $value = $row[$key];
            if (is_string($value) && trim($value) !== '') {
                return trim($value);
            }
        }
        return '';
    }

    private function aysa_monitoring_extract_overview_numeric($row, $keys) {
        if (!is_array($row)) {
            return null;
        }
        foreach ($keys as $key) {
            if (!array_key_exists($key, $row)) {
                continue;
            }
            $value = $row[$key];
            if ($value === null || $value === '') {
                continue;
            }
            if (is_numeric($value)) {
                return floatval($value);
            }
            if (is_string($value)) {
                $normalized = str_replace(',', '.', preg_replace('/[^0-9,\.\-]/', '', $value));
                if ($normalized !== '' && is_numeric($normalized)) {
                    return floatval($normalized);
                }
            }
        }
        return null;
    }

    private function aysa_monitoring_normalize_overview_rows($rows, $associations) {
        $normalized = array();
        if (!is_array($rows)) {
            return $normalized;
        }

        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }

            $keyword = $this->aysa_monitoring_extract_overview_string($row, array('keyword', 'keyword_value', 'query'));
            if ($keyword === '' && isset($row['keyword_data']) && is_array($row['keyword_data'])) {
                $keyword = $this->aysa_monitoring_extract_overview_string($row['keyword_data'], array('keyword', 'keyword_value', 'query'));
            }
            if ($keyword === '') {
                continue;
            }

            $assoc = $this->aysa_monitoring_extract_assoc_for_keyword($associations, $keyword);

            $intent = $this->aysa_monitoring_extract_overview_string($row, array('intent', 'search_intent', 'main_intent'));
            if ($intent === '' && isset($row['search_intent_info']) && is_array($row['search_intent_info'])) {
                $intent = $this->aysa_monitoring_extract_overview_string($row['search_intent_info'], array('main_intent', 'intent'));
            }

            $volume = $this->aysa_monitoring_extract_overview_numeric($row, array('search_volume', 'volume'));
            if (is_null($volume) && isset($row['keyword_info']) && is_array($row['keyword_info'])) {
                $volume = $this->aysa_monitoring_extract_overview_numeric($row['keyword_info'], array('search_volume', 'volume'));
            }

            $competition = $this->aysa_monitoring_extract_overview_numeric($row, array('kd', 'keyword_difficulty', 'competition'));
            if (is_null($competition) && isset($row['keyword_info']) && is_array($row['keyword_info'])) {
                $competition = $this->aysa_monitoring_extract_overview_numeric($row['keyword_info'], array('competition'));
            }

            $traffic = $this->aysa_monitoring_extract_overview_numeric($row, array('traffic', 'clicks', 'gsc_clicks'));
            $traffic_pct = $this->aysa_monitoring_extract_overview_numeric($row, array('traffic_pct', 'traffic_percent'));
            $position = $this->aysa_monitoring_extract_overview_numeric($row, array('position', 'rank', 'rank_group', 'last_position'));
            $position_absolute = $this->aysa_monitoring_extract_overview_numeric($row, array('position_absolute', 'rank_absolute'));
            $url = $this->aysa_monitoring_extract_overview_string($row, array('url', 'target_url', 'found_url', 'link'));
            $ctr = $this->aysa_monitoring_extract_overview_numeric($row, array('ctr', 'gsc_ctr'));
            $impressions = $this->aysa_monitoring_extract_overview_numeric($row, array('impressions', 'gsc_impressions'));
            $cpc = $this->aysa_monitoring_extract_overview_numeric($row, array('cpc', 'cpc_usd', 'cpcUsd'));
            if (is_null($cpc) && isset($row['keyword_info']) && is_array($row['keyword_info'])) {
                $cpc = $this->aysa_monitoring_extract_overview_numeric($row['keyword_info'], array('cpc', 'cpc_usd', 'cpcUsd'));
            }

            $item_types = array();
            if (isset($row['serp_features']) && is_array($row['serp_features'])) {
                $item_types = array_values(array_filter(array_map('strval', $row['serp_features']), function($v) { return trim($v) !== ''; }));
            } elseif (isset($row['item_types']) && is_array($row['item_types'])) {
                $item_types = array_values(array_filter(array_map('strval', $row['item_types']), function($v) { return trim($v) !== ''; }));
            } elseif (isset($row['item_types']) && is_string($row['item_types']) && trim($row['item_types']) !== '') {
                $item_types = array_values(array_filter(array_map('trim', explode(',', (string) $row['item_types'])), function($v) { return $v !== ''; }));
            }

            $normalized[] = array(
                'keyword' => $keyword,
                'associated_on' => (string) ($row['associated_on'] ?? $assoc['associated_on'] ?? 'other'),
                'id_intern' => intval($row['id_intern'] ?? $assoc['id_intern'] ?? 0),
                'link' => $url !== '' ? $url : (string) ($assoc['link'] ?? ''),
                'position' => $position,
                'position_absolute' => $position_absolute,
                'ctr' => $ctr,
                'search_volume' => is_null($volume) ? null : intval(round($volume)),
                'gsc_clicks' => is_null($traffic) ? null : intval(round($traffic)),
                'gsc_impressions' => is_null($impressions) ? null : intval(round($impressions)),
                'item_types' => $item_types,
                'intent' => $intent,
                'kd' => $competition,
                'cpc' => $cpc,
                'traffic_pct' => $traffic_pct,
            );
        }

        $total_traffic = 0.0;
        $has_explicit_traffic_pct = false;
        foreach ($normalized as $row) {
            $clicks = isset($row['gsc_clicks']) ? intval($row['gsc_clicks']) : 0;
            $total_traffic += max(0, $clicks);
            if (isset($row['traffic_pct']) && !is_null($row['traffic_pct'])) {
                $has_explicit_traffic_pct = true;
            }
        }

        if (!$has_explicit_traffic_pct && $total_traffic > 0) {
            foreach ($normalized as $i => $row) {
                $clicks = isset($row['gsc_clicks']) ? intval($row['gsc_clicks']) : 0;
                $normalized[$i]['traffic_pct'] = round(($clicks / $total_traffic) * 100, 4);
            }
        }

        return $normalized;
    }

    private function aysa_keyword_discovery_extract_metrics_rows($result) {
        if (!is_array($result)) {
            return array();
        }
        if (isset($result['rows']) && is_array($result['rows'])) {
            return $result['rows'];
        }
        if (isset($result['results']) && is_array($result['results'])) {
            return $result['results'];
        }
        if (isset($result['data']) && is_array($result['data'])) {
            if (isset($result['data']['rows']) && is_array($result['data']['rows'])) {
                return $result['data']['rows'];
            }
            if (isset($result['data']['results']) && is_array($result['data']['results'])) {
                return $result['data']['results'];
            }
            if (isset($result['data']['items']) && is_array($result['data']['items'])) {
                return $result['data']['items'];
            }
            if (array_keys($result['data']) === range(0, count($result['data']) - 1)) {
                return $result['data'];
            }
        }
        if (isset($result['items']) && is_array($result['items'])) {
            return $result['items'];
        }
        return array();
    }

    private function aysa_keyword_discovery_normalize_metrics_rows($rows) {
        $metrics = array();
        if (!is_array($rows)) {
            return $metrics;
        }

        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keyword = $this->aysa_monitoring_extract_overview_string($row, array('keyword', 'keyword_value', 'query'));
            if ($keyword === '' && isset($row['keyword_data']) && is_array($row['keyword_data'])) {
                $keyword = $this->aysa_monitoring_extract_overview_string($row['keyword_data'], array('keyword', 'keyword_value', 'query'));
            }
            if ($keyword === '') {
                continue;
            }

            $intent = $this->aysa_monitoring_extract_overview_string($row, array('intent', 'search_intent', 'main_intent'));
            if ($intent === '' && isset($row['search_intent_info']) && is_array($row['search_intent_info'])) {
                $intent = $this->aysa_monitoring_extract_overview_string($row['search_intent_info'], array('main_intent', 'intent'));
            }

            $volume = $this->aysa_monitoring_extract_overview_numeric($row, array('search_volume', 'volume'));
            if (is_null($volume) && isset($row['keyword_info']) && is_array($row['keyword_info'])) {
                $volume = $this->aysa_monitoring_extract_overview_numeric($row['keyword_info'], array('search_volume', 'volume'));
            }

            $kd = $this->aysa_monitoring_extract_overview_numeric($row, array('kd', 'keyword_difficulty', 'competition'));
            if (is_null($kd) && isset($row['keyword_info']) && is_array($row['keyword_info'])) {
                $kd = $this->aysa_monitoring_extract_overview_numeric($row['keyword_info'], array('keyword_difficulty', 'competition'));
            }

            $cpc = $this->aysa_monitoring_extract_overview_numeric($row, array('cpc', 'cpc_usd', 'cpcUsd'));
            if (is_null($cpc) && isset($row['keyword_info']) && is_array($row['keyword_info'])) {
                $cpc = $this->aysa_monitoring_extract_overview_numeric($row['keyword_info'], array('cpc', 'cpc_usd', 'cpcUsd'));
            }

            $traffic = $this->aysa_monitoring_extract_overview_numeric($row, array('traffic', 'clicks', 'gsc_clicks'));
            $traffic_pct = $this->aysa_monitoring_extract_overview_numeric($row, array('traffic_pct', 'traffic_percent'));

            $metrics[strtolower(trim($keyword))] = array(
                'intent' => $intent !== '' ? $intent : null,
                'search_volume' => is_null($volume) ? null : intval(round($volume)),
                'kd' => is_null($kd) ? null : round(floatval($kd), 4),
                'cpc' => is_null($cpc) ? null : round(floatval($cpc), 4),
                'traffic' => is_null($traffic) ? null : intval(round($traffic)),
                'traffic_pct' => is_null($traffic_pct) ? null : round(floatval($traffic_pct), 4),
            );
        }

        return $metrics;
    }

    private function aysa_keyword_discovery_normalize_intent($intent) {
        $value = strtolower(trim((string) $intent));
        if ($value === '') {
            return '';
        }
        if ($value === 'informational intent') {
            return 'informational';
        }
        if ($value === 'navigational intent') {
            return 'navigational';
        }
        if ($value === 'commercial intent') {
            return 'commercial';
        }
        if ($value === 'transactional intent') {
            return 'transactional';
        }
        return $value;
    }

    private function aysa_keyword_discovery_normalize_intent_probabilities($value) {
        if (is_string($value) && trim($value) !== '') {
            $decoded = json_decode($value, true);
            if (is_array($decoded)) {
                $value = $decoded;
            }
        }

        if (!is_array($value)) {
            return array();
        }

        $normalized = array();
        $target_keys = array('informational', 'navigational', 'commercial', 'transactional');

        foreach ($target_keys as $key) {
            if (!array_key_exists($key, $value)) {
                continue;
            }
            $raw = $value[$key];
            if ($raw === null || $raw === '' || !is_numeric($raw)) {
                continue;
            }
            $normalized[$key] = round(floatval($raw), 6);
        }

        if (!empty($normalized)) {
            return $normalized;
        }

        foreach ($value as $k => $v) {
            if (is_array($v)) {
                if (isset($v['probability']) && is_numeric($v['probability'])) {
                    $kk = $this->aysa_keyword_discovery_normalize_intent($k);
                    if (in_array($kk, $target_keys, true)) {
                        $normalized[$kk] = round(floatval($v['probability']), 6);
                    }
                }
                continue;
            }
            if (!is_numeric($v)) {
                continue;
            }
            $kk = $this->aysa_keyword_discovery_normalize_intent($k);
            if (in_array($kk, $target_keys, true)) {
                $normalized[$kk] = round(floatval($v), 6);
            }
        }

        if (!empty($normalized)) {
            return $normalized;
        }

        foreach ($value as $row) {
            if (!is_array($row)) {
                continue;
            }
            $name = $this->aysa_keyword_discovery_normalize_intent(
                $row['type'] ?? $row['intent'] ?? $row['main_intent'] ?? ''
            );
            $prob = $row['probability'] ?? $row['score'] ?? null;
            if ($name === '' || !in_array($name, $target_keys, true) || !is_numeric($prob)) {
                continue;
            }
            $normalized[$name] = round(floatval($prob), 6);
        }

        return $normalized;
    }

    private function aysa_keyword_discovery_fetch_keyword_intent_bulk($project_id, $keywords, $context) {
        $project_id = intval($project_id);
        if ($project_id <= 0 || !is_array($keywords) || empty($keywords)) {
            return array();
        }

        $language_code = trim((string) ($context['language_code'] ?? 'ro'));
        if ($language_code === '') {
            $language_code = 'ro';
        }

        $result_map = array();
        $chunks = array_chunk(array_values($keywords), 1000);
        $paths = array(
            '/v1/keywords/intent/bulk',
            '/v1/dataforseo/keywords/intent/bulk',
            '/v1/dataforseo/search-intent/bulk',
        );

        foreach ($chunks as $chunk) {
            if (empty($chunk)) {
                continue;
            }
            $payload = array(
                'project_id' => $project_id,
                'language_code' => $language_code,
                'keywords' => array_values($chunk),
            );

            $rows = array();
            foreach ($paths as $path) {
                $result = $this->aysa_broker_request('POST', $path, $payload, 120);
                if (is_wp_error($result)) {
                    continue;
                }
                $rows = $this->aysa_keyword_discovery_extract_metrics_rows($result);
                if (!empty($rows)) {
                    break;
                }
            }

            if (empty($rows)) {
                continue;
            }

            foreach ($rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $keyword = $this->aysa_monitoring_extract_overview_string($row, array('keyword', 'keyword_value', 'query'));
                if ($keyword === '' && isset($row['keyword_data']) && is_array($row['keyword_data'])) {
                    $keyword = $this->aysa_monitoring_extract_overview_string($row['keyword_data'], array('keyword', 'keyword_value', 'query'));
                }
                if ($keyword === '') {
                    continue;
                }

                $intent = $this->aysa_monitoring_extract_overview_string($row, array('main_intent', 'intent', 'search_intent'));
                $intent_probs = $this->aysa_keyword_discovery_normalize_intent_probabilities(
                    $row['intent_probabilities'] ?? $row['probabilities'] ?? array()
                );

                if (isset($row['search_intent_info']) && is_array($row['search_intent_info'])) {
                    if ($intent === '') {
                        $intent = $this->aysa_monitoring_extract_overview_string($row['search_intent_info'], array('main_intent', 'intent'));
                    }
                    if (empty($intent_probs)) {
                        $intent_probs = $this->aysa_keyword_discovery_normalize_intent_probabilities(
                            $row['search_intent_info']['probabilities'] ?? $row['search_intent_info']
                        );
                    }
                }

                $intent = $this->aysa_keyword_discovery_normalize_intent($intent);
                if ($intent === '' && !empty($intent_probs)) {
                    $best_key = '';
                    $best_val = -INF;
                    foreach ($intent_probs as $k => $v) {
                        if (floatval($v) > $best_val) {
                            $best_key = (string) $k;
                            $best_val = floatval($v);
                        }
                    }
                    if ($best_key !== '') {
                        $intent = $best_key;
                    }
                }

                if ($intent === '' && empty($intent_probs)) {
                    continue;
                }

                $result_map[strtolower(trim($keyword))] = array(
                    'intent' => ($intent !== '' ? $intent : null),
                    'intent_probabilities' => $intent_probs,
                    'intent_source' => 'bulk_intent',
                    'intent_updated_at' => current_time('mysql'),
                );
            }
        }

        return $result_map;
    }

    private function aysa_keyword_discovery_fetch_keyword_metrics($project_id, $keywords, $context) {
        $project_id = intval($project_id);
        if ($project_id <= 0 || !is_array($keywords) || empty($keywords)) {
            return array();
        }

        $payload = array(
            'project_id' => $project_id,
            'target' => (string) ($context['target'] ?? ''),
            'se_domain' => (string) ($context['se_domain'] ?? 'google.com'),
            'location_code' => intval($context['location_code'] ?? 2840),
            'language_code' => (string) ($context['language_code'] ?? 'en'),
            'max_keywords' => max(1, min(1000, count($keywords))),
            'keywords' => array_values($keywords),
        );

        $paths = array(
            '/v1/keywords/overview',
            '/v1/dataforseo/keywords/overview',
        );

        foreach ($paths as $path) {
            $result = $this->aysa_broker_request('POST', $path, $payload, 120);
            if (is_wp_error($result)) {
                continue;
            }
            $rows = $this->aysa_keyword_discovery_extract_metrics_rows($result);
            $normalized = $this->aysa_keyword_discovery_normalize_metrics_rows($rows);
            if (!empty($normalized)) {
                return $normalized;
            }
        }

        // Fallback to monitoring overview endpoint on the same broker when dedicated keyword-overview paths return empty.
        $associations = array();
        foreach ($keywords as $kw) {
            $keyword = trim((string) $kw);
            if ($keyword === '') {
                continue;
            }
            $associations[] = array(
                'keyword' => $keyword,
                'associated_on' => 'other',
                'id_intern' => 0,
                'link' => '',
            );
        }

        if (!empty($associations)) {
            $overview_payload = array(
                'project_id' => $project_id,
                'target' => (string) ($context['target'] ?? ''),
                'se_domain' => (string) ($context['se_domain'] ?? 'google.com'),
                'location_code' => intval($context['location_code'] ?? 2840),
                'language_code' => (string) ($context['language_code'] ?? 'en'),
                'max_keywords' => max(1, min(1000, count($associations))),
                'include_serp_info' => false,
                'associations' => $associations,
            );
            $overview_result = $this->aysa_broker_request('POST', '/v1/monitoring/keywords/overview', $overview_payload, 120);
            if (!is_wp_error($overview_result)) {
                $overview_rows = $this->aysa_monitoring_extract_overview_rows($overview_result);
                $overview_rows = $this->aysa_monitoring_normalize_overview_rows($overview_rows, $associations);
                $normalized = $this->aysa_keyword_discovery_normalize_metrics_rows($overview_rows);
                if (!empty($normalized)) {
                    return $normalized;
                }
            }
        }

        // Last fallback: existing keyword research endpoint (already used in plugin flows).
        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }
        $auth_manager = Aysa_Auth_Manager::get_instance();
        $fallback_metrics = array();
        $chunks = array_chunk(array_values($keywords), 200);
        $location_name = (string) ($context['location_name'] ?? 'Romania');
        if ($location_name === '') {
            $location_name = 'Romania';
        }
        $language_name = (string) ($context['language_name'] ?? 'Romanian');
        if ($language_name === '') {
            $language_name = 'Romanian';
        }

        foreach ($chunks as $chunk) {
            if (empty($chunk)) {
                continue;
            }
            $query = http_build_query(array(
                'keywords' => implode(',', $chunk),
                'locationName' => $location_name,
                'languageName' => $language_name,
                'projectId' => $project_id,
                'limit' => count($chunk),
            ));
            $response = $auth_manager->make_authenticated_request('api/DataForSeo/keywords-research-and-analysis?' . $query, 'GET');
            $rows = $this->normalize_aysa_collection_response($response);
            if (empty($rows) && is_array($response)) {
                if (isset($response['data']) && is_array($response['data'])) {
                    $rows = $this->normalize_aysa_collection_response($response['data']);
                } elseif (isset($response['results']) && is_array($response['results'])) {
                    $rows = array_values($response['results']);
                }
            }

            if (empty($rows)) {
                continue;
            }

            foreach ($rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $keyword = $this->aysa_monitoring_extract_overview_string($row, array('keyword', 'keyword_value', 'query'));
                if ($keyword === '') {
                    continue;
                }
                $intent = $this->aysa_monitoring_extract_overview_string($row, array('intent', 'search_intent', 'main_intent'));
                $volume = $this->aysa_monitoring_extract_overview_numeric($row, array('search_volume', 'searchVolume', 'volume'));
                $kd = $this->aysa_monitoring_extract_overview_numeric($row, array('keyword_difficulty', 'difficulty', 'kd', 'competition'));
                $cpc = $this->aysa_monitoring_extract_overview_numeric($row, array('cpc', 'cpc_usd', 'cpcUsd'));
                $traffic = $this->aysa_monitoring_extract_overview_numeric($row, array('traffic', 'clicks', 'gsc_clicks'));
                $traffic_pct = $this->aysa_monitoring_extract_overview_numeric($row, array('traffic_pct', 'trafficPercent', 'traffic_percent'));

                $fallback_metrics[strtolower(trim($keyword))] = array(
                    'intent' => ($intent !== '' ? $intent : null),
                    'search_volume' => is_null($volume) ? null : intval(round($volume)),
                    'kd' => is_null($kd) ? null : round(floatval($kd), 4),
                    'cpc' => is_null($cpc) ? null : round(floatval($cpc), 4),
                    'traffic' => is_null($traffic) ? null : intval(round($traffic)),
                    'traffic_pct' => is_null($traffic_pct) ? null : round(floatval($traffic_pct), 4),
                );
            }
        }

        if (!empty($fallback_metrics)) {
            return $fallback_metrics;
        }

        return array();
    }

    private function aysa_monitoring_keywords_async_status_key($project_id) {
        return 'aysa_monitoring_keywords_async_' . intval($project_id);
    }

    private function aysa_monitoring_keywords_async_get_status($project_id) {
        $status = get_option($this->aysa_monitoring_keywords_async_status_key($project_id), array());
        return is_array($status) ? $status : array();
    }

    private function aysa_monitoring_keywords_async_save_status($project_id, $status) {
        update_option($this->aysa_monitoring_keywords_async_status_key($project_id), is_array($status) ? $status : array(), false);
    }

    private function aysa_monitoring_keywords_async_output_status($status) {
        if (!is_array($status)) {
            return array(
                'status' => 'idle',
                'result' => null,
                'error' => '',
            );
        }
        $clean = $status;
        unset($clean['_request']);
        return $clean;
    }

    private function aysa_monitoring_keywords_prepare_request($input) {
        $project_id = $this->aysa_resolve_installed_project_id(intval($input['project_id'] ?? 0));
        if ($project_id <= 0) {
            return new WP_Error('invalid_project', 'Invalid project id');
        }

        $context = $this->aysa_monitoring_resolve_context((string) ($input['target'] ?? ''));
        $associations_raw = isset($input['associations']) ? wp_unslash($input['associations']) : '';
        $associations = json_decode((string) $associations_raw, true);
        $associations = $this->aysa_monitoring_normalize_associations($associations);
        if (empty($associations)) {
            return new WP_Error('missing_keywords', 'No tracked keywords provided.');
        }

        $payload = array(
            'project_id' => $project_id,
            'target' => $context['target'],
            'se_domain' => $context['se_domain'],
            'location_code' => $context['location_code'],
            'language_code' => $context['language_code'],
            'associations' => $associations,
            'max_keywords' => max(1, min(500, count($associations))),
            'include_serp_info' => true,
        );

        return array(
            'project_id' => $project_id,
            'domain' => (string) ($context['target'] ?? ''),
            'target' => (string) ($context['target'] ?? ''),
            'request_hash' => md5(wp_json_encode($payload)),
            'payload' => $payload,
            'associations' => $associations,
        );
    }

    private function aysa_monitoring_keywords_execute_request($request) {
        if (!is_array($request)) {
            return new WP_Error('invalid_request', 'Invalid keywords overview request.');
        }

        $project_id = intval($request['project_id'] ?? 0);
        $target = (string) ($request['target'] ?? '');
        $payload = isset($request['payload']) && is_array($request['payload']) ? $request['payload'] : array();
        $associations = isset($request['associations']) && is_array($request['associations']) ? $request['associations'] : array();
        if ($project_id <= 0 || $target === '' || empty($payload) || empty($associations)) {
            return new WP_Error('invalid_request', 'Incomplete keywords overview request.');
        }

        $rows = array();
        $result = $this->aysa_broker_request('POST', '/v1/monitoring/keywords/overview', $payload, 120);
        if (!is_wp_error($result)) {
            $rows = $this->aysa_monitoring_extract_overview_rows($result);
            $rows = $this->aysa_monitoring_normalize_overview_rows($rows, $associations);
        }

        if (empty($rows)) {
            $metrics_result = $this->aysa_broker_request('POST', '/v1/keywords/overview', $payload, 120);
            if (!is_wp_error($metrics_result)) {
                $metrics = $this->aysa_keyword_discovery_normalize_metrics_rows(
                    $this->aysa_keyword_discovery_extract_metrics_rows($metrics_result)
                );
                foreach ($associations as $association) {
                    $keyword = trim((string) ($association['keyword'] ?? ''));
                    if ($keyword === '') {
                        continue;
                    }
                    $metric = $metrics[strtolower($keyword)] ?? array();
                    $rows[] = array(
                        'keyword' => $keyword,
                        'intent' => (string) ($metric['intent'] ?? ''),
                        'search_volume' => $metric['search_volume'] ?? null,
                        'kd' => $metric['kd'] ?? null,
                        'traffic' => $metric['traffic'] ?? null,
                        'traffic_pct' => $metric['traffic_pct'] ?? null,
                        'link' => (string) ($association['link'] ?? ''),
                        'item_types' => '',
                        'tracked_at' => current_time('mysql'),
                        '_isRefreshed' => !empty($metric),
                    );
                }
            }
        }

        if (empty($rows)) {
            $error_message = is_wp_error($result) ? $result->get_error_message() : 'empty_keywords_overview';
            return new WP_Error('keywords_overview_failed', 'Keywords overview failed: ' . $error_message);
        }

        $persist_info = array('inserted' => 0, 'failed' => 0, 'last_error' => '');
        if (!empty($rows)) {
            $persist_info = $this->aysa_monitoring_persist_rows($project_id, $target, $rows);
        }

        return array(
            'project_id' => $project_id,
            'target' => $target,
            'rows' => $rows,
            'count' => count($rows),
            'persisted_rows' => intval($persist_info['inserted'] ?? 0),
        );
    }

    public function handle_monitoring_keywords_async_start() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }
        if ($this->aysa_monitoring_block_refresh_until_approval('keywords_overview_refresh')) {
            return;
        }

        $request = $this->aysa_monitoring_keywords_prepare_request($_POST);
        if (is_wp_error($request)) {
            wp_send_json_error($request->get_error_message());
            return;
        }

        $project_id = intval($request['project_id'] ?? 0);
        $job = $this->aysa_project_jobs_enqueue(
            $project_id,
            'monitoring',
            'keywords_overview_refresh',
            array(
                'request' => $request,
            ),
            array(
                'dedupe_key' => (string) ($request['request_hash'] ?? ''),
                'input_summary' => array(
                    'domain' => (string) ($request['domain'] ?? ''),
                ),
            )
        );
        $response = array(
            'project_id' => $project_id,
            'run_id' => (string) ($job['job_id'] ?? ''),
            'status' => (string) ($job['status'] ?? 'queued'),
            'result' => isset($job['result_payload']) ? $job['result_payload'] : null,
            'error' => (string) ($job['error_summary'] ?? ''),
            'worker_ref' => (string) ($job['worker_ref'] ?? ''),
            'worker_status' => (string) ($job['worker_status'] ?? ''),
            'blocked_by' => isset($job['blocked_by']) && is_array($job['blocked_by']) ? array_values($job['blocked_by']) : array(),
            'project_job_rack' => $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state($project_id)),
        );
        wp_send_json_success($response);
    }

    public function handle_monitoring_keywords_async_status() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = intval($this->aysa_resolve_project_id_with_local_fallback());
        if ($project_id <= 0) {
            wp_send_json_error('Invalid monitoring keywords async status request.');
            return;
        }

        $status = $this->aysa_monitoring_keywords_async_get_status($project_id);
        if (empty($status)) {
            $job = $this->aysa_project_jobs_find_latest_job($project_id, 'monitoring', 'keywords_overview_refresh', array('queued', 'running', 'completed', 'failed'));
            if (is_array($job)) {
                wp_send_json_success(array(
                    'project_id' => $project_id,
                    'run_id' => (string) ($job['job_id'] ?? ''),
                    'status' => (string) ($job['status'] ?? 'idle'),
                    'result' => isset($job['result_payload']) ? $job['result_payload'] : null,
                    'error' => (string) ($job['error_summary'] ?? ''),
                    'worker_ref' => (string) ($job['worker_ref'] ?? ''),
                    'worker_status' => (string) ($job['worker_status'] ?? ''),
                    'project_job_rack' => $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state($project_id)),
                ));
                return;
            }
            wp_send_json_success(array(
                'project_id' => $project_id,
                'status' => 'idle',
                'result' => null,
                'error' => '',
                'project_job_rack' => $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state($project_id)),
            ));
            return;
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $requested_run_id = (string) wp_unslash($_POST['run_id'] ?? '');
        if ($requested_run_id !== '' && (string) ($status['run_id'] ?? '') !== $requested_run_id) {
            wp_send_json_success(array(
                'project_id' => $project_id,
                'status' => 'idle',
                'result' => null,
                'error' => '',
            ));
            return;
        }

        $state = (string) ($status['status'] ?? 'idle');
        $updated_at = intval($status['updated_at'] ?? 0);
        $output = $this->aysa_monitoring_keywords_async_output_status($status);
        $output['status_poll_advanced_worker'] = false;
        $output['read_only_contract'] = $this->aysa_monitoring_read_only_contract('keywords_async_status_read_only');
        if (in_array($state, array('queued', 'running'), true) && ($updated_at <= 0 || (time() - $updated_at) > 20) && !empty($status['_request'])) {
            $output['read_only_warning'] = 'Keywords overview is marked running, but status readback will not advance provider work.';
        }
        $output['project_job_rack'] = $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state($project_id));
        wp_send_json_success($output);
    }

    public function run_monitoring_keywords_async_event($payload) {
        if (!is_array($payload)) {
            return;
        }

        $project_id = intval($payload['project_id'] ?? 0);
        $run_id = (string) ($payload['run_id'] ?? '');
        $request_hash = (string) ($payload['request_hash'] ?? '');
        $request = isset($payload['request']) && is_array($payload['request']) ? $payload['request'] : array();
        if ($project_id <= 0 || $run_id === '' || $request_hash === '') {
            return;
        }

        $status = $this->aysa_monitoring_keywords_async_get_status($project_id);
        if (
            empty($status) ||
            (string) ($status['run_id'] ?? '') !== $run_id ||
            (string) ($status['request_hash'] ?? '') !== $request_hash
        ) {
            return;
        }

        $status['status'] = 'running';
        $status['updated_at'] = time();
        $status['error'] = '';
        $this->aysa_monitoring_keywords_async_save_status($project_id, $status);

        $result = $this->aysa_monitoring_keywords_execute_request($request);
        $status['updated_at'] = time();
        $status['finished_at'] = time();
        unset($status['_request']);
        if (is_wp_error($result)) {
            $status['status'] = 'failed';
            $status['result'] = null;
            $status['error'] = $result->get_error_message();
        } else {
            $status['status'] = 'completed';
            $status['result'] = $result;
            $status['error'] = '';
        }
        $this->aysa_monitoring_keywords_async_save_status($project_id, $status);
        $this->aysa_project_jobs_finalize(
            $project_id,
            $this->aysa_project_jobs_make_worker_ref('monitoring_keywords_async', 'keywords_overview_refresh', $run_id),
            (string) $status['status'],
            is_array($status['result'] ?? null) ? $status['result'] : null,
            (string) ($status['error'] ?? '')
        );
    }

    public function handle_monitoring_keywords_overview() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $request = $this->aysa_monitoring_keywords_prepare_request($_POST);
        if (is_wp_error($request)) {
            wp_send_json_error($request->get_error_message());
            return;
        }

        $project_id = intval($request['project_id'] ?? 0);
        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_monitoring_keywords_overview',
            'monitoring_keywords_overview',
            array(
                'module' => 'monitoring_analytics',
                'source' => 'manual_refresh',
            ),
            60,
            '/v1/monitoring/keywords/overview'
        );

        $result = $this->aysa_monitoring_keywords_execute_request($request);
        if (is_wp_error($result)) {
            wp_send_json_error($result->get_error_message());
            return;
        }

        wp_send_json_success($result);
    }

    public function handle_monitoring_get_associations() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        // Do not charge credits for passive page-load bootstrap calls.

        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }

        try {
            $auth_manager = Aysa_Auth_Manager::get_instance();
            $raw = $auth_manager->make_authenticated_request('api/Association?projectId=' . $project_id . '&pageSize=10000', 'GET');
            $rows = $this->normalize_aysa_collection_response($raw);
            if (!is_array($rows)) {
                $rows = array();
            }

            $tracked = array();
            $seen = array();
            foreach ($rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $is_tracked = false;
                if (array_key_exists('isTracked', $row)) {
                    $is_tracked = !empty($row['isTracked']);
                } elseif (array_key_exists('IsTracked', $row)) {
                    $is_tracked = !empty($row['IsTracked']);
                }
                if (!$is_tracked) {
                    continue;
                }

                $keyword = '';
                if (isset($row['keyword'])) {
                    $keyword = trim((string) $row['keyword']);
                } elseif (isset($row['Keyword'])) {
                    $keyword = trim((string) $row['Keyword']);
                }
                if ($keyword === '') {
                    continue;
                }

                $assoc_on = (string) ($row['associatedOn'] ?? $row['AssociatedOn'] ?? 'other');
                $id_intern = intval($row['idIntern'] ?? $row['IdIntern'] ?? 0);
                $link = (string) ($row['link'] ?? $row['Link'] ?? '');
                if ($link === '') {
                    $link = $this->aysa_monitoring_resolve_association_link(
                        $assoc_on,
                        $id_intern,
                        (string) ($row['value'] ?? $row['Value'] ?? '')
                    );
                }
                $dedupe = strtolower($keyword . '|' . $assoc_on . '|' . $id_intern);
                if (isset($seen[$dedupe])) {
                    continue;
                }
                $seen[$dedupe] = true;

                $tracked[] = array(
                    'id' => intval($row['id'] ?? $row['Id'] ?? 0),
                    'keyword' => $keyword,
                    'associated_on' => $assoc_on,
                    'id_intern' => $id_intern,
                    'is_main' => !empty($row['isMain']) || !empty($row['IsMain']),
                    'value' => (string) ($row['value'] ?? $row['Value'] ?? ''),
                    'link' => $link,
                );
            }

            // Local-first fallback: include tracked associations from local DB so Monitoring always has testable keywords.
            $local_rows = $this->get_keyword_discovery_local_associations($project_id);
            $max_local_fallback = 120;
            $added_local = 0;
            foreach ($local_rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                if ($added_local >= $max_local_fallback) {
                    break;
                }
                $keyword = trim((string) ($row['keyword'] ?? ''));
                $assoc_on = (string) ($row['associatedOn'] ?? $row['associated_on'] ?? 'other');
                $id_intern = intval($row['idIntern'] ?? $row['id_intern'] ?? 0);
                if ($keyword === '') {
                    continue;
                }
                $dedupe = strtolower($keyword . '|' . $assoc_on . '|' . $id_intern);
                if (isset($seen[$dedupe])) {
                    continue;
                }
                $link = $this->aysa_monitoring_resolve_association_link(
                    $assoc_on,
                    $id_intern,
                    (string) ($row['value'] ?? '')
                );
                if ($link === '') {
                    continue;
                }
                $seen[$dedupe] = true;
                $tracked[] = array(
                    'id' => intval($row['id'] ?? 0),
                    'keyword' => $keyword,
                    'associated_on' => $assoc_on,
                    'id_intern' => $id_intern,
                    'is_main' => false,
                    'value' => (string) ($row['value'] ?? ''),
                    'link' => $link,
                );
                $added_local++;
            }

            wp_send_json_success(array(
                'project_id' => $project_id,
                'count' => count($tracked),
                'associations' => $tracked,
            ));
        } catch (Exception $e) {
            wp_send_json_error('Failed to fetch associations: ' . $e->getMessage());
        }
    }

    private function aysa_monitoring_resolve_association_link($associated_on, $id_intern, $value = '') {
        $value = trim((string) $value);
        if ($value !== '' && preg_match('#^https?://#i', $value)) {
            return $value;
        }

        $target = $this->resolve_keyword_discovery_entity_target((string) $associated_on, intval($id_intern));
        if (!is_array($target)) {
            return '';
        }

        $kind = (string) ($target['kind'] ?? '');
        $entity_id = intval($target['id'] ?? 0);
        if ($kind === 'post' && $entity_id > 0) {
            $permalink = get_permalink($entity_id);
            return is_string($permalink) ? $permalink : '';
        }
        if ($kind === 'term' && $entity_id > 0) {
            $term_link = get_term_link($entity_id);
            return is_string($term_link) ? $term_link : '';
        }
        $home = home_url('/');
        if (is_string($home) && preg_match('#^https?://#i', $home)) {
            return $home;
        }
        return '';
    }

    public function handle_onpage_get_associations() {
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : \x27\x27;
        $nonce_ok = ($nonce !== '') && (
            wp_verify_nonce($nonce, 'aysa_nonce') ||
            wp_verify_nonce($nonce, 'aysa_metabox_nonce')
        );
        if (!$nonce_ok) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        $local_rows = $this->get_keyword_discovery_local_associations($project_id);
        if (!empty($local_rows)) {
            if (!$this->is_keyword_discovery_entity_meta_synced($project_id)) {
                $this->sync_keyword_discovery_associations_batch_to_entity_meta($local_rows);
                $this->mark_keyword_discovery_entity_meta_synced($project_id);
            }
            wp_send_json_success(array(
                'project_id' => $project_id,
                'associations' => array_values($local_rows),
                'count' => count($local_rows),
                'source' => 'local_db',
            ));
            return;
        }

        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }

        try {
            $auth_manager = Aysa_Auth_Manager::get_instance();
            $raw = $auth_manager->make_authenticated_request(
                'api/Association?projectId=' . $project_id . '&pageSize=10000&pageNumber=1&sortOrder=asc',
                'GET'
            );
            $rows = $this->normalize_aysa_collection_response($raw);
            if (!is_array($rows)) {
                $rows = array();
            }

            $rows = $this->merge_keyword_discovery_associations(
                $rows,
                $local_rows
            );

            // Persist fetched rows locally so next calls remain local-first.
            if (!empty($rows)) {
                $this->save_keyword_discovery_local_associations($project_id, $rows);
            }

            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_onpage_get_associations',
                'onpage_get_associations:' . $project_id,
                array(
                    'module' => 'on_page_optimization',
                    'source' => 'remote_bootstrap',
                    'association_count' => count($rows),
                ),
                5,
                '/api/Association'
            );

            wp_send_json_success(array(
                'project_id' => $project_id,
                'associations' => array_values($rows),
                'count' => count($rows),
                'source' => 'remote_bootstrap',
            ));
        } catch (Exception $e) {
            wp_send_json_success(array(
                'project_id' => $project_id,
                'associations' => array_values($local_rows),
                'count' => count($local_rows),
                'source' => 'local_fallback',
                'warning' => 'Remote association API unavailable: ' . $e->getMessage(),
            ));
        }
    }

    /**
     * Keep On-Page association reads and updates behind the authenticated
     * WordPress boundary so the AYSA bearer credential never enters JavaScript.
     */
    public function handle_onpage_association_proxy() {
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : \x27\x27;
        $nonce_ok = ($nonce !== '') && (
            wp_verify_nonce($nonce, 'aysa_nonce') ||
            wp_verify_nonce($nonce, 'aysa_metabox_nonce')
        );
        if (!$nonce_ok) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = intval(get_option('aysa_project_id', 0));
        $association_id = absint(wp_unslash($_POST['association_id'] ?? 0));
        $operation = sanitize_key((string) wp_unslash($_POST['operation'] ?? ''));
        if ($project_id <= 0 || $association_id <= 0 || !in_array($operation, array('get', 'upsert'), true)) {
            wp_send_json_error('Invalid association request');
            return;
        }

        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }

        $auth_manager = Aysa_Auth_Manager::get_instance();
        $endpoint = 'api/Association/' . $association_id . '?projectId=' . $project_id;
        $current = $auth_manager->make_authenticated_request($endpoint, 'GET');
        if (!is_array($current)) {
            wp_send_json_error('Association unavailable');
            return;
        }

        $remote_project_id = intval($current['projectId'] ?? $current['project_id'] ?? 0);
        $local_match = false;
        foreach ($this->get_keyword_discovery_local_associations($project_id) as $local_row) {
            $local_id = absint($local_row['id'] ?? $local_row['associationId'] ?? 0);
            if ($local_id === $association_id) {
                $local_match = true;
                break;
            }
        }
        if ($remote_project_id !== $project_id && !$local_match) {
            wp_send_json_error('Association is outside the current project');
            return;
        }

        if ($operation === 'get') {
            wp_send_json_success(array('association' => $current));
            return;
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $payload_json = isset($_POST['payload']) ? (string) wp_unslash($_POST['payload']) : '';
        $payload = json_decode($payload_json, true);
        if (!is_array($payload)) {
            wp_send_json_error('Invalid association payload');
            return;
        }
        $payload_id = absint($payload['id'] ?? $payload['associationId'] ?? 0);
        if ($payload_id > 0 && $payload_id !== $association_id) {
            wp_send_json_error('Association id mismatch');
            return;
        }

        // The server-owned binding is authoritative; never trust a browser project hint.
        $payload['id'] = $association_id;
        $payload['projectId'] = $project_id;
        unset($payload['project_id']);

        $updated = $auth_manager->make_authenticated_request('api/Association/upsert', 'PUT', $payload);
        if (!is_array($updated)) {
            wp_send_json_error('Association update failed');
            return;
        }
        wp_send_json_success(array('association' => $updated));
    }

    public function handle_monitoring_get_project_id() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = intval($this->aysa_resolve_project_id_with_local_fallback());
        if ($project_id <= 0) {
            wp_send_json_error('Project ID not configured');
            return;
        }
        wp_send_json_success(array(
            'project_id' => $project_id,
        ));
    }

    public function handle_get_project_by_domain_broker() {
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : \x27\x27;
        $nonce_ok = ($nonce !== '') && (
            wp_verify_nonce($nonce, 'aysa_nonce') ||
            wp_verify_nonce($nonce, 'aysa_metabox_nonce') ||
            wp_verify_nonce($nonce, 'aysa-ajax-nonce') ||
            wp_verify_nonce($nonce, 'aysa_wizard_nonce') ||
            wp_verify_nonce($nonce, 'aysa_internal_linking_nonce')
        );
        if (!$nonce_ok) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $domain = isset($_POST['domain']) ? sanitize_text_field((string) wp_unslash($_POST['domain'])) : '';
        if ($domain === '') {
            $domain = (string) wp_parse_url(home_url('/'), PHP_URL_HOST);
        }
        $domain = strtolower(trim($domain));
        $domain = preg_replace('#^https?://#i', '', $domain);
        $domain = trim((string) $domain, "/ \t\n\r\0\x0B");
        if ($domain === '') {
            wp_send_json_error('Invalid domain');
            return;
        }

        $project_id = intval(get_option('aysa_project_id', 0));
        $source = 'local_option';
        $warning = '';
        $host = (string) wp_parse_url(home_url('/'), PHP_URL_HOST);
        $is_local = ($host === 'localhost' || $host === '127.0.0.1' || substr((string) $host, -10) === '.localhost');

        if ($project_id <= 0 && $is_local) {
            $project_id = defined('AYSA_LOCAL_DEV_PROJECT_ID') ? intval(AYSA_LOCAL_DEV_PROJECT_ID) : 22;
            $source = 'local_dev_fallback';
        }

        $paths = array(
            '/v1/project/by-domain',
            '/v1/projects/by-domain',
            '/v1/project/resolve-domain',
        );
        $payload = array(
            'domain' => $domain,
            'host' => $domain,
            'site_url' => home_url('/'),
        );
        foreach ($paths as $path) {
            $result = $this->aysa_broker_request('POST', $path, $payload, 20);
            if (is_wp_error($result)) {
                $warning = $this->aysa_humanize_broker_error($result, 'Broker unavailable');
                continue;
            }
            $candidate = 0;
            if (is_array($result)) {
                $candidate = intval($result['project_id'] ?? $result['projectId'] ?? $result['id'] ?? 0);
                if ($candidate <= 0 && !empty($result['data']) && is_array($result['data'])) {
                    $candidate = intval($result['data']['project_id'] ?? $result['data']['projectId'] ?? $result['data']['id'] ?? 0);
                }
            }
            if ($candidate > 0) {
                $project_id = $candidate;
                $source = 'broker';
                update_option('aysa_project_id', $project_id, false);
                break;
            }
        }

        if ($project_id <= 0) {
            wp_send_json_error('Project ID could not be resolved for this domain.');
            return;
        }

        wp_send_json_success(array(
            'project_id' => $project_id,
            'domain' => $domain,
            'source' => $source,
            'warning' => $warning,
        ));
    }

    public function handle_monitoring_get_context() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $context = $this->aysa_monitoring_resolve_context((string) wp_unslash($_POST['target'] ?? ''));
        wp_send_json_success($context);
    }

    public function handle_monitoring_get_ai_overview() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $payload = $this->aysa_monitoring_build_ai_payload();
        $fallback = $this->aysa_monitoring_ai_get_local_overview($payload['project_id'], $payload['domain']);
        if (!empty($fallback)) {
            $fallback['source'] = 'local_ai_snapshot_history';
            $fallback['read_only_contract'] = $this->aysa_monitoring_read_only_contract('ai_overview_local_only');
            wp_send_json_success($fallback);
            return;
        }
        wp_send_json_success(array(
            'ok' => true,
            'project_id' => intval($payload['project_id'] ?? 0),
            'domain' => (string) ($payload['domain'] ?? ''),
            'snapshot' => array(),
            'history' => array(),
            'status' => 'ai_snapshot_missing',
            'message' => 'Nu există încă un snapshot AI salvat în siguranță. Nu afișez vizibilitate AI până nu rulezi o verificare aprobată.',
            'read_only_contract' => $this->aysa_monitoring_read_only_contract('ai_overview_snapshot_missing'),
        ));
    }

    public function handle_monitoring_refresh_ai_snapshot() {
        $this->handle_monitoring_start_ai_refresh();
    }

    public function handle_monitoring_start_ai_refresh() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }
        if ($this->aysa_monitoring_block_refresh_until_approval('ai_visibility_refresh')) {
            return;
        }

        $payload = $this->aysa_monitoring_build_ai_payload();
        $project_id = intval($payload['project_id'] ?? 0);
        $domain = (string) ($payload['domain'] ?? '');
        if ($project_id <= 0 || $domain === '') {
            wp_send_json_error('AI refresh cannot start without project and domain context.');
            return;
        }

        $job = $this->aysa_project_jobs_enqueue(
            $project_id,
            'monitoring',
            'ai_refresh',
            $payload,
            array(
                'dedupe_key' => md5(wp_json_encode($payload)),
                'input_summary' => array(
                    'domain' => $domain,
                ),
            )
        );
        wp_send_json_success(array(
            'project_id' => $project_id,
            'domain' => $domain,
            'run_id' => (string) ($job['job_id'] ?? ''),
            'status' => (string) ($job['status'] ?? 'queued'),
            'error' => (string) ($job['error_summary'] ?? ''),
            'worker_ref' => (string) ($job['worker_ref'] ?? ''),
            'worker_status' => (string) ($job['worker_status'] ?? ''),
            'project_job_rack' => $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state($project_id)),
        ));
    }

    public function handle_monitoring_get_ai_refresh_status() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $payload = $this->aysa_monitoring_build_ai_payload();
        $project_id = intval($payload['project_id'] ?? 0);
        $domain = (string) ($payload['domain'] ?? '');
        $status_key = $this->aysa_monitoring_ai_refresh_status_option_key($project_id, $domain);
        $existing = get_option($status_key, array());
        if (!is_array($existing)) {
            $existing = array();
        }
        $status = get_option($status_key, array());
        if (!is_array($status) || empty($status)) {
            $status = array(
                'ok' => true,
                'project_id' => $project_id,
                'domain' => $domain,
                'status' => 'idle',
                'message' => 'No AI refresh in progress.',
                'updated_at' => '',
            );
        }

        $status_value = (string) ($status['status'] ?? 'idle');
        $fallback = $this->aysa_monitoring_ai_get_local_overview($project_id, $domain);
        if (!empty($fallback)) {
            if (!in_array($status_value, array('queued', 'running'), true) || intval($status['snapshot_id'] ?? 0) > 0) {
                $status['snapshot'] = $fallback['snapshot'] ?? null;
            }
            $status['history'] = $fallback['history'] ?? array();
        }

        if (($status['status'] ?? '') === 'running') {
            $status['message'] = 'AI refresh is still marked running. Status readback is read-only and will not advance or schedule work.';
        }

        if (($status['status'] ?? 'idle') === 'idle') {
            $job = $this->aysa_project_jobs_find_latest_job($project_id, 'monitoring', 'ai_refresh', array('queued', 'running', 'completed', 'failed'));
            if (is_array($job)) {
                $status = array(
                    'project_id' => $project_id,
                    'domain' => $domain,
                    'status' => (string) ($job['status'] ?? 'idle'),
                    'error' => (string) ($job['error_summary'] ?? ''),
                    'worker_ref' => (string) ($job['worker_ref'] ?? ''),
                );
            }
        }

        $status['status_poll_advanced_worker'] = false;
        $status['read_only_contract'] = $this->aysa_monitoring_read_only_contract('ai_refresh_status_read_only');
        $status['project_job_rack'] = $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state($project_id));
        wp_send_json_success($status);
    }

    public function handle_monitoring_get_ai_snapshot_history() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $payload = $this->aysa_monitoring_build_ai_payload();
        $payload['limit'] = intval(wp_unslash($_POST['limit'] ?? 12));
        wp_send_json_success(array(
            'ok' => true,
            'project_id' => intval($payload['project_id']),
            'domain' => (string) $payload['domain'],
            'history' => $this->aysa_monitoring_ai_get_local_history($payload['project_id'], $payload['domain'], $payload['limit']),
            'source' => 'local_ai_snapshot_history',
            'read_only_contract' => $this->aysa_monitoring_read_only_contract('ai_history_local_only'),
        ));
    }

    public function handle_monitoring_get_ai_snapshot_detail() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $payload = $this->aysa_monitoring_build_ai_payload();
        $payload['snapshot_id'] = intval(wp_unslash($_POST['snapshot_id'] ?? 0));
        $payload['item_id'] = intval(wp_unslash($_POST['item_id'] ?? 0));
        $history = $this->aysa_monitoring_ai_get_local_history($payload['project_id'], $payload['domain'], 30);
        foreach ($history as $row) {
            if (!is_array($row)) {
                continue;
            }
            if (intval($row['snapshot_id'] ?? 0) !== intval($payload['snapshot_id'])) {
                continue;
            }
            wp_send_json_success(array(
                'ok' => true,
                'project_id' => intval($payload['project_id']),
                'domain' => (string) $payload['domain'],
                'snapshot_id' => intval($payload['snapshot_id']),
                'snapshot' => is_array($row['_snapshot'] ?? null) ? $row['_snapshot'] : array(),
                'source' => 'local_ai_snapshot_history',
                'read_only_contract' => $this->aysa_monitoring_read_only_contract('ai_detail_local_only'),
            ));
            return;
        }
        wp_send_json_error(array(
            'code' => 'ai_snapshot_missing',
            'message' => 'AI snapshot detail is not available from stored local history.',
            'read_only_contract' => $this->aysa_monitoring_read_only_contract('ai_detail_snapshot_missing'),
        ));
    }

    private function aysa_monitoring_ai_async_supported_tasks() {
        return array('overview', 'history', 'detail');
    }

    private function aysa_monitoring_ai_async_status_key($project_id, $task) {
        return 'aysa_monitoring_ai_async_' . sanitize_key((string) $task) . '_' . intval($project_id);
    }

    private function aysa_monitoring_ai_async_get_status($project_id, $task) {
        $raw = get_option($this->aysa_monitoring_ai_async_status_key($project_id, $task), array());
        return is_array($raw) ? $raw : array();
    }

    private function aysa_monitoring_ai_async_save_status($project_id, $task, $status) {
        update_option($this->aysa_monitoring_ai_async_status_key($project_id, $task), is_array($status) ? $status : array(), false);
    }

    private function aysa_monitoring_ai_async_schedule_event($payload, $delay_seconds = 1) {
        if (!is_array($payload)) {
            return;
        }
        $timestamp = time() + max(1, intval($delay_seconds));
        wp_schedule_single_event($timestamp, 'aysa_monitoring_ai_async_event', array($payload));
        if (function_exists('spawn_cron')) {
            spawn_cron();
        }
    }

    private function aysa_monitoring_ai_async_prepare_request($task, $input) {
        $payload = $this->aysa_monitoring_build_ai_payload();
        $project_id = intval($payload['project_id'] ?? 0);
        $domain = (string) ($payload['domain'] ?? '');
        if ($project_id <= 0 || $domain === '') {
            return new WP_Error('invalid_context', 'AI monitoring async request requires project and domain context.');
        }

        $request = array(
            'task' => $task,
            'project_id' => $project_id,
            'domain' => $domain,
            'payload' => $payload,
        );

        if ($task === 'history') {
            $request['payload']['limit'] = intval($input['limit'] ?? 12);
        } elseif ($task === 'detail') {
            $request['payload']['snapshot_id'] = intval($input['snapshot_id'] ?? 0);
            $request['payload']['item_id'] = intval($input['item_id'] ?? 0);
            if (intval($request['payload']['snapshot_id']) <= 0 && intval($request['payload']['item_id']) <= 0) {
                return new WP_Error('invalid_detail_request', 'AI detail requires snapshot_id or item_id.');
            }
        }

        $request['request_hash'] = md5(wp_json_encode(array(
            'task' => $task,
            'payload' => $request['payload'],
        )));

        return $request;
    }

    private function aysa_monitoring_ai_async_execute($request) {
        $task = sanitize_key((string) ($request['task'] ?? ''));
        $payload = isset($request['payload']) && is_array($request['payload']) ? $request['payload'] : array();
        $project_id = intval($request['project_id'] ?? ($payload['project_id'] ?? 0));
        $domain = (string) ($request['domain'] ?? ($payload['domain'] ?? ''));

        switch ($task) {
            case 'overview':
                $result = $this->aysa_broker_request('POST', '/v1/monitoring/ai/overview', $payload, 120);
                if (is_wp_error($result)) {
                    $fallback = $this->aysa_monitoring_ai_get_local_overview($project_id, $domain);
                    if (!empty($fallback)) {
                        return $fallback;
                    }
                    return $result;
                }
                $snapshot = isset($result['snapshot']) && is_array($result['snapshot']) ? $result['snapshot'] : array();
                if (!empty($snapshot)) {
                    $this->aysa_monitoring_ai_persist_snapshot_summary($project_id, $domain, $snapshot);
                }
                return $result;

            case 'history':
                $result = $this->aysa_broker_request('POST', '/v1/monitoring/ai/history', $payload, 120);
                if (is_wp_error($result)) {
                    return array(
                        'ok' => true,
                        'project_id' => $project_id,
                        'domain' => $domain,
                        'history' => $this->aysa_monitoring_ai_get_local_history($project_id, $domain, intval($payload['limit'] ?? 12)),
                    );
                }
                return $result;

            case 'detail':
                $result = $this->aysa_broker_request('POST', '/v1/monitoring/ai/detail', $payload, 120);
                if (is_wp_error($result)) {
                    return $result;
                }
                return $result;
        }

        return new WP_Error('unsupported_task', 'Unsupported monitoring AI async task.');
    }

    private function aysa_monitoring_ai_async_output_status($status) {
        if (!is_array($status)) {
            return array();
        }
        $clean = $status;
        unset($clean['_request']);
        return $clean;
    }

    private function aysa_monitoring_rank_refresh_start_queued($payload) {
        $project_id = intval($payload['project_id'] ?? 0);
        $associations = isset($payload['associations']) && is_array($payload['associations']) ? array_values($payload['associations']) : array();
        if ($project_id <= 0 || empty($associations)) {
            return new WP_Error('invalid_monitoring_rank_request', 'No tracked keywords provided.');
        }

        $existing_status = $this->aysa_monitoring_get_refresh_status($project_id);
        if (($existing_status['status'] ?? '') === 'running') {
            return array(
                'status' => 'running',
                'worker_ref' => $this->aysa_project_jobs_make_worker_ref('monitoring_rank_refresh', 'rank_refresh', (string) ($existing_status['run_id'] ?? '')),
                'worker_status' => (string) ($existing_status['status'] ?? ''),
                'result_summary' => array('already_running' => true),
                'started_at' => intval($existing_status['started_at'] ?? time()),
            );
        }

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_monitoring_rank_refresh_start_background',
            'monitoring_rank_refresh_start_background',
            array(
                'module' => 'monitoring_analytics',
                'source' => 'background_refresh',
            ),
            120,
            '/v1/dataforseo/rank-tracking/refresh-start-background'
        );

        $batch_size = intval($payload['batch_size'] ?? 10);
        if ($batch_size < 5) {
            $batch_size = 5;
        } elseif ($batch_size > 10) {
            $batch_size = 10;
        }

        $context = isset($payload['context']) && is_array($payload['context'])
            ? $payload['context']
            : $this->aysa_monitoring_resolve_context((string) ($payload['target'] ?? ''));
        $run_id = uniqid('mon_', true);
        $now = time();

        $queue = array(
            'run_id' => $run_id,
            'project_id' => $project_id,
            'context' => $context,
            'associations' => $associations,
            'cursor' => 0,
            'batch_size' => $batch_size,
            'current_retry' => 0,
            'created_at' => $now,
        );
        update_option($this->aysa_monitoring_refresh_queue_key($project_id), $queue, false);

        $status = array(
            'run_id' => $run_id,
            'status' => 'running',
            'total' => count($associations),
            'processed' => 0,
            'refreshed' => 0,
            'failed' => 0,
            'deferred' => 0,
            'last_error' => '',
            'started_at' => $now,
            'updated_at' => $now,
            'finished_at' => 0,
        );
        $this->aysa_monitoring_save_refresh_status($project_id, $status);
        $this->aysa_monitoring_ensure_refresh_cron_tick($project_id);
        $this->aysa_monitoring_schedule_background_refresh($project_id, 1);

        return array(
            'status' => 'running',
            'worker_ref' => $this->aysa_project_jobs_make_worker_ref('monitoring_rank_refresh', 'rank_refresh', $run_id),
            'worker_status' => 'running',
            'result_summary' => array('tracked_keywords' => count($associations)),
            'started_at' => $now,
        );
    }

    private function aysa_monitoring_ai_refresh_start_queued($payload) {
        $project_id = intval($payload['project_id'] ?? 0);
        $domain = (string) ($payload['domain'] ?? '');
        if ($project_id <= 0 || $domain === '') {
            return new WP_Error('invalid_monitoring_ai_request', 'AI refresh cannot start without project and domain context.');
        }

        $status_key = $this->aysa_monitoring_ai_refresh_status_option_key($project_id, $domain);
        $result = $this->aysa_broker_request('POST', '/v1/monitoring/ai/refresh/start', $payload, 60);
        if (is_wp_error($result)) {
            return $result;
        }
        if (!is_array($result) || empty($result['ok'])) {
            return new WP_Error('monitoring_ai_refresh_failed', isset($result['error']) ? (string) $result['error'] : 'AI refresh failed.');
        }

        $snapshot = isset($result['snapshot']) && is_array($result['snapshot']) ? $result['snapshot'] : array();
        if (!empty($snapshot)) {
            $this->aysa_monitoring_ai_persist_snapshot_summary($project_id, $domain, $snapshot);
        }

        $worker_ref = $this->aysa_project_jobs_make_worker_ref('monitoring_ai_async', 'ai_refresh', (string) md5($project_id . '|' . strtolower($domain)));
        $status = array(
            'ok' => true,
            'project_id' => $project_id,
            'domain' => $domain,
            'status' => (string) ($result['status'] ?? 'running'),
            'message' => (string) ($result['message'] ?? 'AI refresh started.'),
            'started_at' => gmdate('c'),
            'updated_at' => gmdate('c'),
            'snapshot_id' => intval($result['snapshot_id'] ?? 0),
            'error' => '',
            'processed_questions' => intval($result['processed_questions'] ?? 0),
            'total_questions' => intval($result['total_questions'] ?? 0),
            'processed_sources' => intval($result['processed_sources'] ?? 0),
            'total_sources' => intval($result['total_sources'] ?? 0),
            'partial_errors' => isset($result['partial_errors']) && is_array($result['partial_errors']) ? array_values($result['partial_errors']) : array(),
            'query_pack' => isset($result['query_pack']) && is_array($result['query_pack']) ? array_values($result['query_pack']) : array(),
            'overview_questions' => isset($result['overview_questions']) && is_array($result['overview_questions']) ? array_values($result['overview_questions']) : array(),
            'snapshot' => $snapshot,
            'history' => isset($result['history']) && is_array($result['history']) ? array_values($result['history']) : array(),
            'billed_provider_milli_cents' => 0,
            'orchestrator_worker_ref' => $worker_ref,
        );
        $status = $this->aysa_monitoring_ai_finalize_status_payload($status, array());
        update_option($status_key, $status, false);
        $this->aysa_monitoring_schedule_ai_refresh_event($payload, 1);

        return array(
            'status' => in_array((string) ($status['status'] ?? ''), array('queued', 'running'), true) ? 'running' : (string) ($status['status'] ?? 'running'),
            'worker_ref' => $worker_ref,
            'worker_status' => (string) ($status['status'] ?? 'running'),
            'result_summary' => array('domain' => $domain),
            'started_at' => time(),
        );
    }

    public function handle_monitoring_ai_async_start() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $task = sanitize_key((string) wp_unslash($_POST['task'] ?? ''));
        if (!in_array($task, $this->aysa_monitoring_ai_async_supported_tasks(), true)) {
            wp_send_json_error('Unsupported monitoring AI task.');
            return;
        }

        $request = $this->aysa_monitoring_ai_async_prepare_request($task, $_POST);
        if (is_wp_error($request)) {
            wp_send_json_error($request->get_error_message());
            return;
        }

        $project_id = intval($request['project_id'] ?? 0);
        $request_hash = (string) ($request['request_hash'] ?? '');
        $existing = $this->aysa_monitoring_ai_async_get_status($project_id, $task);
        if (
            is_array($existing) &&
            !empty($existing['run_id']) &&
            in_array((string) ($existing['status'] ?? ''), array('queued', 'running'), true) &&
            (string) ($existing['request_hash'] ?? '') === $request_hash
        ) {
            wp_send_json_success($this->aysa_monitoring_ai_async_output_status($existing));
            return;
        }

        $run_id = uniqid('mon_ai_', true);
        $status = array(
            'task' => $task,
            'project_id' => $project_id,
            'domain' => (string) ($request['domain'] ?? ''),
            'run_id' => $run_id,
            'request_hash' => $request_hash,
            'status' => 'queued',
            'result' => null,
            'error' => '',
            'started_at' => time(),
            'updated_at' => time(),
            'finished_at' => 0,
            '_request' => $request,
        );
        $this->aysa_monitoring_ai_async_save_status($project_id, $task, $status);
        $this->aysa_monitoring_ai_async_schedule_event(array(
            'task' => $task,
            'project_id' => $project_id,
            'run_id' => $run_id,
            'request_hash' => $request_hash,
            'request' => $request,
        ), 1);

        wp_send_json_success($this->aysa_monitoring_ai_async_output_status($status));
    }

    public function handle_monitoring_ai_async_status() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $task = sanitize_key((string) wp_unslash($_POST['task'] ?? ''));
        $project_id = intval($this->aysa_resolve_project_id_with_local_fallback());
        if ($project_id <= 0 || !in_array($task, $this->aysa_monitoring_ai_async_supported_tasks(), true)) {
            wp_send_json_error('Invalid monitoring AI async status request.');
            return;
        }

        $status = $this->aysa_monitoring_ai_async_get_status($project_id, $task);
        if (empty($status)) {
            wp_send_json_success(array(
                'task' => $task,
                'project_id' => $project_id,
                'status' => 'idle',
                'result' => null,
                'error' => '',
            ));
            return;
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $requested_run_id = (string) wp_unslash($_POST['run_id'] ?? '');
        if ($requested_run_id !== '' && (string) ($status['run_id'] ?? '') !== $requested_run_id) {
            wp_send_json_success(array(
                'task' => $task,
                'project_id' => $project_id,
                'status' => 'idle',
                'result' => null,
                'error' => '',
            ));
            return;
        }

        $state = (string) ($status['status'] ?? 'idle');
        $updated_at = intval($status['updated_at'] ?? 0);
        if (in_array($state, array('queued', 'running'), true) && ($updated_at <= 0 || (time() - $updated_at) > 20) && !empty($status['_request'])) {
            $this->run_monitoring_ai_async_event(array(
                'task' => $task,
                'project_id' => $project_id,
                'run_id' => (string) ($status['run_id'] ?? ''),
                'request_hash' => (string) ($status['request_hash'] ?? ''),
                'request' => is_array($status['_request']) ? $status['_request'] : array(),
            ));
            $status = $this->aysa_monitoring_ai_async_get_status($project_id, $task);
        }

        wp_send_json_success($this->aysa_monitoring_ai_async_output_status($status));
    }

    public function run_monitoring_ai_async_event($payload) {
        if (!is_array($payload)) {
            return;
        }

        $task = sanitize_key((string) ($payload['task'] ?? ''));
        $project_id = $this->aysa_resolve_installed_project_id(intval($payload['project_id'] ?? 0));
        $run_id = (string) ($payload['run_id'] ?? '');
        $request_hash = (string) ($payload['request_hash'] ?? '');
        $request = isset($payload['request']) && is_array($payload['request']) ? $payload['request'] : array();
        if ($project_id <= 0 || $run_id === '' || $request_hash === '' || !in_array($task, $this->aysa_monitoring_ai_async_supported_tasks(), true)) {
            return;
        }

        $status = $this->aysa_monitoring_ai_async_get_status($project_id, $task);
        if (
            empty($status) ||
            (string) ($status['run_id'] ?? '') !== $run_id ||
            (string) ($status['request_hash'] ?? '') !== $request_hash
        ) {
            return;
        }

        $status['status'] = 'running';
        $status['updated_at'] = time();
        $status['error'] = '';
        $this->aysa_monitoring_ai_async_save_status($project_id, $task, $status);

        $result = $this->aysa_monitoring_ai_async_execute($request);
        $status['updated_at'] = time();
        $status['finished_at'] = time();
        unset($status['_request']);
        if (is_wp_error($result)) {
            $status['status'] = 'failed';
            $status['result'] = null;
            $status['error'] = $result->get_error_message();
        } else {
            $status['status'] = 'completed';
            $status['result'] = $result;
            $status['error'] = '';
        }
        $this->aysa_monitoring_ai_async_save_status($project_id, $task, $status);
    }

    public function handle_monitoring_save_ai_query_pack() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $payload = $this->aysa_monitoring_build_ai_payload();
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $raw_query_pack = isset($_POST['query_pack']) ? wp_unslash($_POST['query_pack']) : '[]';
        $query_pack = json_decode((string) $raw_query_pack, true);
        if (!is_array($query_pack)) {
            wp_send_json_error('Invalid query pack payload.');
            return;
        }
        $payload['query_pack'] = $query_pack;

        $result = $this->aysa_broker_request('POST', '/v1/monitoring/ai/query-pack', $payload, 120);
        if (is_wp_error($result)) {
            wp_send_json_error('AI query pack save failed: ' . $result->get_error_message());
            return;
        }

        $this->aysa_wallet_track_action_usage(
            intval($payload['project_id'] ?? 0),
            'aysa_monitoring_save_ai_query_pack',
            'monitoring_ai_query_pack_save',
            array(
                'module' => 'monitoring_analytics',
                'source' => 'manual_save',
                'query_count' => count($query_pack),
            ),
            10,
            '/v1/monitoring/ai/query-pack'
        );

        wp_send_json_success($result);
    }

    private function aysa_sitemap_competition_build_payload() {
        $project_id = intval($this->aysa_resolve_project_id_with_local_fallback(intval(get_option('aysa_project_id', 0))));
        $target_host = $this->aysa_monitoring_extract_host((string) home_url('/'));

        return array(
            'project_id' => $project_id,
            'target_host' => $target_host,
            'competitors' => $this->aysa_sitemap_competition_resolve_competitors($project_id),
        );
    }

    private function aysa_sitemap_competition_resolve_competitors($project_id) {
        $project_id = intval($project_id);
        $domains = array();

        $seed_user_id = get_current_user_id();
        $seed_data = $this->aysa_keyword_strategy_extract_seed_data($seed_user_id);
        $seed_competitors = isset($seed_data['competitors']) && is_array($seed_data['competitors']) ? $seed_data['competitors'] : array();
        foreach ($seed_competitors as $competitor) {
            $host = $this->aysa_monitoring_extract_host((string) $competitor);
            if ($host !== '') {
                $domains[$host] = true;
            }
        }

        $cached_identification = $this->aysa_competitor_identification_get_cached_payload($project_id);
        $cached_data = isset($cached_identification['data']) && is_array($cached_identification['data']) ? $cached_identification['data'] : array();
        $unique_competitors = isset($cached_data['unique_competitors']) && is_array($cached_data['unique_competitors'])
            ? $cached_data['unique_competitors']
            : array();
        foreach ($unique_competitors as $row) {
            if (is_array($row)) {
                $host = $this->aysa_monitoring_extract_host((string) ($row['domain'] ?? $row['host'] ?? $row['competitor'] ?? ''));
            } else {
                $host = $this->aysa_monitoring_extract_host((string) $row);
            }
            if ($host !== '') {
                $domains[$host] = true;
            }
        }

        $home_host = $this->aysa_monitoring_extract_host((string) home_url('/'));
        if ($home_host !== '') {
            unset($domains[$home_host]);
        }

        if (empty($domains)) {
            global $wpdb;
            $runs_table = esc_sql($this->aysa_sitemap_competition_runs_table());
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $runs_table));
            if ($exists === $runs_table) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $previous = $wpdb->get_var($wpdb->prepare(
                    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                    "SELECT competitors_json
" .
                    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
                    "                     FROM {$runs_table}
                     WHERE project_id = %d
                       AND competitors_json IS NOT NULL
                       AND competitors_json <> ''
                       AND competitors_json <> '[]'
                     ORDER BY id DESC
                     LIMIT 1",
                    $project_id
                ));
                $decoded = json_decode((string) $previous, true);
                if (is_array($decoded)) {
                    foreach ($decoded as $competitor) {
                        $host = $this->aysa_monitoring_extract_host((string) $competitor);
                        if ($host !== '' && $host !== $home_host) {
                            $domains[$host] = true;
                        }
                    }
                }
            }
        }

        return array_values(array_keys($domains));
    }

    private function aysa_sitemap_competition_normalize_freshness_signal($item) {
        if (!is_array($item)) {
            return '';
        }
        foreach (array('updated_signal', 'publication_signal', 'lastmod') as $key) {
            $value = trim((string) ($item[$key] ?? ''));
            if ($value !== '') {
                return $value;
            }
        }
        return '';
    }

    private function aysa_sitemap_competition_local_inventory() {
        $posts = get_posts(array(
            'post_type' => array('post', 'page'),
            'post_status' => 'publish',
            'posts_per_page' => 300,
            'orderby' => 'modified',
            'order' => 'DESC',
            // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters -- Sitemap competition compares the installation-wide public inventory across language filters and is explicitly capped at 300 posts.
            'suppress_filters' => true,
        ));

        $inventory = array();
        foreach ($posts as $post) {
            if (!$post instanceof WP_Post) {
                continue;
            }
            $inventory[] = array(
                'post_id' => intval($post->ID),
                'title' => get_the_title($post),
                'url' => get_permalink($post),
                'slug' => $post->post_name,
                'modified_gmt' => (string) $post->post_modified_gmt,
                'tokens' => $this->aysa_sitemap_competition_tokens(get_the_title($post) . ' ' . $post->post_name),
            );
        }

        return $inventory;
    }

    private function aysa_sitemap_competition_tokens($value) {
        $value = strtolower(remove_accents((string) $value));
        $value = preg_replace('/[^a-z0-9]+/i', ' ', $value);
        $parts = preg_split('/\s+/', trim((string) $value)) ?: array();
        $parts = array_values(array_filter($parts, function($part) {
            return strlen((string) $part) >= 3;
        }));
        return array_values(array_unique($parts));
    }

    private function aysa_sitemap_competition_similarity($left_tokens, $right_tokens) {
        $left = is_array($left_tokens) ? array_values(array_unique($left_tokens)) : array();
        $right = is_array($right_tokens) ? array_values(array_unique($right_tokens)) : array();
        if (empty($left) || empty($right)) {
            return 0.0;
        }
        $intersection = array_intersect($left, $right);
        $union = array_unique(array_merge($left, $right));
        if (empty($union)) {
            return 0.0;
        }
        return count($intersection) / count($union);
    }

    private function aysa_sitemap_competition_build_todo_rows($items) {
        $local_inventory = $this->aysa_sitemap_competition_local_inventory();
        $todo_rows = array();

        foreach ($items as $item) {
            if (!is_array($item) || (string) ($item['content_type'] ?? '') !== 'blog_post') {
                continue;
            }
            $title = trim((string) ($item['title'] ?? $item['meta_title'] ?? ''));
            $topic = $title !== '' ? $title : trim((string) ($item['slug'] ?? ''));
            $tokens = $this->aysa_sitemap_competition_tokens($topic . ' ' . (string) ($item['path'] ?? ''));
            $best = null;
            $bestScore = 0.0;
            foreach ($local_inventory as $local) {
                $score = $this->aysa_sitemap_competition_similarity($tokens, $local['tokens']);
                if ($score > $bestScore) {
                    $bestScore = $score;
                    $best = $local;
                }
            }

            $local_status = 'missing';
            $suggested_action = 'create article';
            if ($best !== null && $bestScore >= 0.75) {
                $local_status = 'covered';
                $suggested_action = 'review manually';
                $competitorTs = strtotime($this->aysa_sitemap_competition_normalize_freshness_signal($item));
                $localTs = strtotime((string) ($best['modified_gmt'] ?? ''));
                if ($competitorTs && $localTs && $competitorTs > $localTs) {
                    $local_status = 'needs_refresh';
                    $suggested_action = 'update existing article';
                }
            } elseif ($best !== null && $bestScore >= 0.4) {
                $local_status = 'partially_covered';
                $suggested_action = 'update existing article';
            }

            $todo_rows[] = array(
                'competitor_source' => (string) ($item['competitor'] ?? ''),
                'competitor_url' => (string) ($item['url'] ?? ''),
                'competitor_title' => $title,
                'estimated_topic' => $topic,
                'freshness_signal' => $this->aysa_sitemap_competition_normalize_freshness_signal($item),
                'local_status' => $local_status,
                'suggested_action' => $suggested_action,
                'matched_post_id' => intval($best['post_id'] ?? 0),
                'matched_url' => (string) ($best['url'] ?? ''),
                'score' => round($bestScore, 4),
            );
        }

        return $todo_rows;
    }

    private function aysa_sitemap_competition_persist_broker_result($project_id, $target_host, $result, $preferred_local_run_id = 0) {
        global $wpdb;
        $project_id = intval($project_id);
        $target_host = strtolower(trim((string) $target_host));
        if ($project_id <= 0 || $target_host === '' || !is_array($result)) {
            return array();
        }

        $this->aysa_sitemap_competition_maybe_create_tables();
        $runs_table = esc_sql($this->aysa_sitemap_competition_runs_table());
        $items_table = esc_sql($wpdb->prefix . 'aysa_sitemap_competition_items');
        $todo_table = esc_sql($this->aysa_sitemap_competition_todo_table());

        $run = isset($result['run']) && is_array($result['run']) ? $result['run'] : array();
        if (empty($run)) {
            return array();
        }
        $remote_run_id = intval($run['run_id'] ?? 0);
        $items = isset($result['items']) && is_array($result['items']) ? array_values($result['items']) : array();
        $todo_rows = $this->aysa_sitemap_competition_build_todo_rows($items);

        $existing_local_id = intval($preferred_local_run_id);
        if ($existing_local_id <= 0 && $remote_run_id > 0) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $existing_local_id = intval($wpdb->get_var($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT id FROM {$runs_table} WHERE project_id = %d AND target_host = %s AND remote_run_id = %d LIMIT 1",
                $project_id,
                $target_host,
                $remote_run_id
            )));
        }

        $overview = isset($run['overview']) && is_array($run['overview']) ? $run['overview'] : array();
        $summary = isset($run['competitor_summaries']) && is_array($run['competitor_summaries']) ? $run['competitor_summaries'] : array();
        $data = array(
            'project_id' => $project_id,
            'target_host' => $target_host,
            'remote_run_id' => $remote_run_id,
            'status' => sanitize_text_field((string) ($run['status'] ?? '')),
            'competitors_json' => wp_json_encode(isset($run['competitors']) && is_array($run['competitors']) ? $run['competitors'] : array()),
            'summary_json' => wp_json_encode($summary),
            'overview_json' => wp_json_encode($overview),
            'run_json' => wp_json_encode($run),
            'started_at' => !empty($run['started_at']) ? gmdate('Y-m-d H:i:s', strtotime((string) $run['started_at'])) : current_time('mysql'),
            'completed_at' => !empty($run['completed_at']) ? gmdate('Y-m-d H:i:s', strtotime((string) $run['completed_at'])) : null,
            'created_at' => current_time('mysql'),
            'updated_at' => current_time('mysql'),
        );

        if ($existing_local_id > 0) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->update($runs_table, $data, array('id' => $existing_local_id));
            $local_run_id = $existing_local_id;
        } else {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->insert($runs_table, $data);
            $local_run_id = intval($wpdb->insert_id);
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $wpdb->delete($items_table, array('local_run_id' => $local_run_id));
        foreach ($items as $item) {
            if (!is_array($item)) {
                continue;
            }
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->insert($items_table, array(
                'local_run_id' => $local_run_id,
                'project_id' => $project_id,
                'target_host' => $target_host,
                'competitor_host' => (string) ($item['competitor'] ?? ''),
                'final_url' => (string) ($item['url'] ?? ''),
                'page_title' => (string) ($item['title'] ?? ''),
                'meta_title' => (string) ($item['meta_title'] ?? ''),
                'content_type' => (string) ($item['content_type'] ?? 'unknown'),
                'freshness_signal' => $this->aysa_sitemap_competition_normalize_freshness_signal($item),
                'slug' => (string) ($item['slug'] ?? ''),
                'path' => (string) ($item['path'] ?? ''),
                'fetch_status' => (string) ($item['fetch_status'] ?? ''),
                'item_json' => wp_json_encode($item),
                'created_at' => current_time('mysql'),
            ));
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $wpdb->delete($todo_table, array('local_run_id' => $local_run_id));
        foreach ($todo_rows as $todo_row) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->insert($todo_table, array(
                'local_run_id' => $local_run_id,
                'project_id' => $project_id,
                'target_host' => $target_host,
                'competitor_host' => (string) ($todo_row['competitor_source'] ?? ''),
                'competitor_url' => (string) ($todo_row['competitor_url'] ?? ''),
                'competitor_title' => (string) ($todo_row['competitor_title'] ?? ''),
                'estimated_topic' => (string) ($todo_row['estimated_topic'] ?? ''),
                'freshness_signal' => (string) ($todo_row['freshness_signal'] ?? ''),
                'local_status' => (string) ($todo_row['local_status'] ?? 'missing'),
                'suggested_action' => (string) ($todo_row['suggested_action'] ?? 'review manually'),
                'matched_post_id' => intval($todo_row['matched_post_id'] ?? 0),
                'matched_url' => (string) ($todo_row['matched_url'] ?? ''),
                'todo_json' => wp_json_encode($todo_row),
                'created_at' => current_time('mysql'),
            ));
        }

        $payload = array(
            'project_id' => $project_id,
            'target_host' => $target_host,
            'updated_at' => current_time('mysql'),
            'competitors' => isset($run['competitors']) && is_array($run['competitors']) ? $run['competitors'] : array(),
            'inventory_summary' => isset($overview['inventory_summary']) && is_array($overview['inventory_summary']) ? $overview['inventory_summary'] : array(),
            'content_inventory_summary' => isset($overview['inventory_summary']) && is_array($overview['inventory_summary']) ? $overview['inventory_summary'] : array(),
            'todo_summary' => $this->aysa_sitemap_competition_build_todo_summary($todo_rows),
            'todo_items' => array_slice($todo_rows, 0, 20),
        );
        $this->aysa_sitemap_competition_sync_context_to_broker($payload);

        return $this->aysa_sitemap_competition_get_local_overview($project_id, $target_host);
    }

    private function aysa_sitemap_competition_get_local_run_row($local_run_id) {
        global $wpdb;
        $runs_table = esc_sql($this->aysa_sitemap_competition_runs_table());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$runs_table} WHERE id = %d LIMIT 1", intval($local_run_id)), ARRAY_A);
        return is_array($row) ? $row : array();
    }

    private function aysa_sitemap_competition_update_local_run($local_run_id, array $patch = array()) {
        global $wpdb;
        $runs_table = esc_sql($this->aysa_sitemap_competition_runs_table());
        $row = $this->aysa_sitemap_competition_get_local_run_row($local_run_id);
        if (!is_array($row) || empty($row)) {
            return;
        }

        $run_json = json_decode((string) ($row['run_json'] ?? '{}'), true);
        if (!is_array($run_json)) {
            $run_json = array();
        }
        $overview = json_decode((string) ($row['overview_json'] ?? '{}'), true);
        if (!is_array($overview)) {
            $overview = array();
        }

        $status = isset($patch['status']) ? (string) $patch['status'] : (string) ($row['status'] ?? '');
        if ($status !== '') {
            $run_json['status'] = $status;
            $overview['status'] = $status;
        }

        if (array_key_exists('error', $patch)) {
            $error_message = (string) $patch['error'];
            if ($error_message !== '') {
                $run_json['error'] = $error_message;
                $overview['error'] = $error_message;
            } else {
                unset($run_json['error'], $overview['error']);
            }
        }

        foreach (array('phase', 'attempt', 'recovery_count', 'last_recovered_at', 'progress_label', 'last_worker_error') as $key) {
            if (array_key_exists($key, $patch)) {
                $value = $patch[$key];
                if ($value === null || $value === '') {
                    unset($run_json[$key], $overview[$key]);
                } else {
                    $run_json[$key] = $value;
                    $overview[$key] = $value;
                }
            }
        }

        $data = array(
            'status' => $status,
            'run_json' => wp_json_encode($run_json),
            'overview_json' => wp_json_encode($overview),
            'updated_at' => current_time('mysql'),
        );

        if (array_key_exists('remote_run_id', $patch)) {
            $data['remote_run_id'] = intval($patch['remote_run_id']);
        }

        if (array_key_exists('completed_at', $patch)) {
            $data['completed_at'] = $patch['completed_at'];
        } elseif (in_array($status, array('completed', 'partial', 'failed'), true)) {
            $data['completed_at'] = current_time('mysql');
        } else {
            $data['completed_at'] = null;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $wpdb->update($runs_table, $data, array('id' => intval($local_run_id)));
    }

    private function aysa_sitemap_competition_create_local_run($project_id, $target_host, $payload) {
        global $wpdb;
        $this->aysa_sitemap_competition_maybe_create_tables();
        $runs_table = esc_sql($this->aysa_sitemap_competition_runs_table());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $wpdb->insert($runs_table, array(
            'project_id' => intval($project_id),
            'target_host' => (string) $target_host,
            'remote_run_id' => 0,
            'status' => 'queued',
            'competitors_json' => wp_json_encode(isset($payload['competitors']) && is_array($payload['competitors']) ? $payload['competitors'] : array()),
            'summary_json' => wp_json_encode(array()),
            'overview_json' => wp_json_encode(array(
                'status' => 'queued',
                'competitor_count' => count(isset($payload['competitors']) && is_array($payload['competitors']) ? $payload['competitors'] : array()),
                'inventory_summary' => array(),
                'competitor_summaries' => array(),
            )),
            'run_json' => wp_json_encode(array(
                'status' => 'queued',
                'phase' => 'dispatch',
                'attempt' => 0,
                'recovery_count' => 0,
                'progress_label' => 'Queued',
                'competitors' => isset($payload['competitors']) && is_array($payload['competitors']) ? $payload['competitors'] : array(),
            )),
            'started_at' => current_time('mysql'),
            'completed_at' => null,
            'created_at' => current_time('mysql'),
            'updated_at' => current_time('mysql'),
        ));
        return intval($wpdb->insert_id);
    }

    private function aysa_sitemap_competition_update_local_run_status($local_run_id, $status, $error_message = '') {
        $this->aysa_sitemap_competition_update_local_run($local_run_id, array(
            'status' => (string) $status,
            'error' => (string) $error_message,
        ));
    }

    private function aysa_sitemap_competition_schedule_scan_event($payload, $delay_seconds = 1) {
        if (!is_array($payload)) {
            return;
        }
        if (!wp_next_scheduled('aysa_sitemap_competition_scan_event', array($payload))) {
            wp_schedule_single_event(time() + max(1, intval($delay_seconds)), 'aysa_sitemap_competition_scan_event', array($payload));
        }
        if (function_exists('spawn_cron')) {
            spawn_cron();
        }
    }

    private function aysa_sitemap_competition_start_queued_scan($payload) {
        if (!is_array($payload)) {
            return new WP_Error('invalid_sitemap_payload', 'Invalid sitemap competition payload.');
        }
        $project_id = intval($payload['project_id'] ?? 0);
        $target_host = (string) ($payload['target_host'] ?? '');
        if ($project_id <= 0 || $target_host === '') {
            return new WP_Error('invalid_sitemap_payload', 'Invalid sitemap competition payload.');
        }

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_sitemap_competition_start_scan',
            'sitemap_competition_start_scan',
            array(
                'module' => 'research',
                'tab' => 'sitemap_competition',
                'target_host' => $target_host,
                'competitors_count' => isset($payload['competitors']) && is_array($payload['competitors']) ? count($payload['competitors']) : 0,
                'source' => 'project_orchestrator',
            ),
            45,
            '/v1/research/sitemap-competition/start'
        );

        $local_run_id = $this->aysa_sitemap_competition_create_local_run($project_id, $target_host, $payload);
        $event_payload = array(
            'project_id' => $project_id,
            'target_host' => $target_host,
            'competitors' => isset($payload['competitors']) && is_array($payload['competitors']) ? array_values($payload['competitors']) : array(),
            'local_run_id' => $local_run_id,
            'phase' => 'dispatch',
            'attempt' => 1,
        );
        $this->aysa_sitemap_competition_schedule_scan_event($event_payload, 1);

        return array(
            'queued' => true,
            'local_run_id' => $local_run_id,
        );
    }

    private function aysa_sitemap_competition_maybe_recover_local_run($project_id, $target_host, $local) {
        if (!is_array($local) || empty($local['run']) || !is_array($local['run'])) {
            return $local;
        }

        $run = $local['run'];
        $local_run_id = intval($run['local_run_id'] ?? 0);
        $status = (string) ($run['status'] ?? '');
        if ($local_run_id <= 0 || !in_array($status, array('queued', 'running'), true)) {
            return $local;
        }

        $updated_at_raw = (string) ($run['updated_at'] ?? '');
        $updated_at_ts = $updated_at_raw !== '' ? strtotime($updated_at_raw) : 0;
        if ($updated_at_ts > 0 && (time() - $updated_at_ts) <= 30) {
            return $local;
        }

        $run_meta = is_array($run['overview'] ?? null) ? $run['overview'] : array();
        $recovery_count = intval($run_meta['recovery_count'] ?? 0);
        $phase = (string) ($run_meta['phase'] ?? (($status === 'queued') ? 'dispatch' : 'advance'));
        if ($recovery_count >= 3) {
            $this->aysa_sitemap_competition_update_local_run($local_run_id, array(
                'status' => 'failed',
                'error' => 'Sitemap competition worker stalled and was stopped. Please retry.',
                'phase' => $phase,
                'last_worker_error' => 'stale_stop',
            ));
            return $this->aysa_sitemap_competition_get_local_overview($project_id, $target_host);
        }

        $retry_payload = array(
            'project_id' => intval($project_id),
            'target_host' => (string) $target_host,
            'competitors' => isset($run['competitors']) && is_array($run['competitors']) ? array_values($run['competitors']) : array(),
            'local_run_id' => $local_run_id,
            'phase' => $phase,
            'attempt' => 1,
            'remote_run_id' => intval($run['remote_run_id'] ?? 0),
        );

        $this->aysa_sitemap_competition_update_local_run($local_run_id, array(
            'status' => 'queued',
            'error' => '',
            'phase' => $phase,
            'recovery_count' => $recovery_count + 1,
            'last_recovered_at' => current_time('mysql'),
            'progress_label' => $phase === 'dispatch' ? 'Retrying broker handoff' : 'Retrying worker advance',
        ));
        $this->aysa_sitemap_competition_schedule_scan_event($retry_payload, 1);

        return $this->aysa_sitemap_competition_get_local_overview($project_id, $target_host);
    }

    private function aysa_sitemap_competition_build_todo_summary($todo_rows) {
        $summary = array(
            'covered' => 0,
            'partially_covered' => 0,
            'missing' => 0,
            'needs_refresh' => 0,
            'total' => 0,
        );
        foreach ((array) $todo_rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $status = (string) ($row['local_status'] ?? 'missing');
            if (!isset($summary[$status])) {
                continue;
            }
            $summary[$status]++;
            $summary['total']++;
        }
        return $summary;
    }

    private function aysa_sitemap_competition_build_local_inventory_rows($local_run_id) {
        global $wpdb;
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT competitor_host, content_type, COUNT(*) AS total
             FROM {$wpdb->prefix}aysa_sitemap_competition_items
             WHERE local_run_id = %d
             GROUP BY competitor_host, content_type
             ORDER BY competitor_host ASC, total DESC",
            intval($local_run_id)
        ), ARRAY_A);
        return is_array($rows) ? array_values($rows) : array();
    }

    private function aysa_sitemap_competition_sync_context_to_broker($payload) {
        if (!is_array($payload)) {
            return;
        }
        $result = $this->aysa_broker_request('POST', '/v1/project-context/sitemap-competition/upsert', $payload, 20);
        if (is_wp_error($result)) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Release-reviewed diagnostic only; the message contains no credentials or request payloads and runs only in opt-in debugging or the administrative uninstall lifecycle.
            error_log('AYSA sitemap competition context broker sync failed: ' . $result->get_error_message());
        }
    }

    private function aysa_sitemap_competition_get_local_overview($project_id, $target_host) {
        global $wpdb;
        $runs_table = esc_sql($this->aysa_sitemap_competition_runs_table());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $runs_table));
        if ($exists !== $runs_table) {
            return array();
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $row = $wpdb->get_row($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT * FROM {$runs_table} WHERE project_id = %d AND target_host = %s ORDER BY id DESC LIMIT 1",
            intval($project_id),
            (string) $target_host
        ), ARRAY_A);
        if (!is_array($row)) {
            return array();
        }

        $local_run_id = intval($row['id'] ?? 0);
        $items_table = esc_sql($this->aysa_sitemap_competition_items_table());
        $todo_table = esc_sql($this->aysa_sitemap_competition_todo_table());

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $todo_rows = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT todo_json FROM {$todo_table} WHERE local_run_id = %d ORDER BY id ASC LIMIT 200",
            $local_run_id
        ), ARRAY_A);
        $todos = array();
        foreach ((array) $todo_rows as $todo_row) {
            $decoded = json_decode((string) ($todo_row['todo_json'] ?? '{}'), true);
            if (is_array($decoded)) {
                $todos[] = $decoded;
            }
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $history_rows = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT id, remote_run_id, status, overview_json, started_at, completed_at, updated_at FROM {$runs_table} WHERE project_id = %d AND target_host = %s ORDER BY id DESC LIMIT 10",
            intval($project_id),
            (string) $target_host
        ), ARRAY_A);
        $history = array();
        foreach ((array) $history_rows as $history_row) {
            $overview = json_decode((string) ($history_row['overview_json'] ?? '{}'), true);
            $history[] = array(
                'local_run_id' => intval($history_row['id'] ?? 0),
                'remote_run_id' => intval($history_row['remote_run_id'] ?? 0),
                'status' => (string) ($history_row['status'] ?? ''),
                'overview' => is_array($overview) ? $overview : array(),
                'article_count' => intval($overview['inventory_summary']['blog_post'] ?? $overview['inventory_summary']['article'] ?? 0),
                'started_at' => (string) ($history_row['started_at'] ?? ''),
                'completed_at' => (string) ($history_row['completed_at'] ?? ''),
                'updated_at' => (string) ($history_row['updated_at'] ?? ''),
            );
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $item_count = intval($wpdb->get_var($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT COUNT(*) FROM {$items_table} WHERE local_run_id = %d",
            $local_run_id
        )));
        $overview = json_decode((string) ($row['overview_json'] ?? '{}'), true);
        if (!is_array($overview)) {
            $overview = array();
        }
        $inventory_summary = isset($overview['inventory_summary']) && is_array($overview['inventory_summary']) ? $overview['inventory_summary'] : array();
        $inventory_total = 0;
        foreach ($inventory_summary as $count) {
            $inventory_total += max(0, intval($count));
        }

        return array(
            'ok' => true,
            'project_id' => intval($project_id),
            'target_host' => (string) $target_host,
            'run' => array(
                'local_run_id' => $local_run_id,
                'remote_run_id' => intval($row['remote_run_id'] ?? 0),
                'status' => (string) ($row['status'] ?? ''),
                'competitors' => json_decode((string) ($row['competitors_json'] ?? '[]'), true),
                'overview' => $overview,
                'summary' => json_decode((string) ($row['summary_json'] ?? '[]'), true),
                'started_at' => (string) ($row['started_at'] ?? ''),
                'completed_at' => (string) ($row['completed_at'] ?? ''),
                'updated_at' => (string) ($row['updated_at'] ?? ''),
                'item_count' => $inventory_total > 0 ? $inventory_total : $item_count,
            ),
            'inventory_rows' => $this->aysa_sitemap_competition_build_local_inventory_rows($local_run_id),
            'todo_rows' => $todos,
            'todo_summary' => $this->aysa_sitemap_competition_build_todo_summary($todos),
            'history' => $history,
        );
    }

    private function aysa_sitemap_competition_get_local_detail($project_id, $target_host, $local_run_id = 0) {
        global $wpdb;
        $runs_table = esc_sql($this->aysa_sitemap_competition_runs_table());
        $items_table = esc_sql($this->aysa_sitemap_competition_items_table());
        $todo_table = esc_sql($this->aysa_sitemap_competition_todo_table());

        if ($local_run_id <= 0) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $local_run_id = intval($wpdb->get_var($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT id FROM {$runs_table} WHERE project_id = %d AND target_host = %s ORDER BY id DESC LIMIT 1",
                intval($project_id),
                (string) $target_host
            )));
        }
        if ($local_run_id <= 0) {
            return array();
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $item_rows = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT item_json FROM {$items_table} WHERE local_run_id = %d ORDER BY competitor_host ASC, final_url ASC LIMIT 1000",
            $local_run_id
        ), ARRAY_A);
        $items = array();
        foreach ((array) $item_rows as $item_row) {
            $decoded = json_decode((string) ($item_row['item_json'] ?? '{}'), true);
            if (is_array($decoded)) {
                $items[] = $decoded;
            }
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $todo_rows = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT todo_json FROM {$todo_table} WHERE local_run_id = %d ORDER BY id ASC LIMIT 500",
            $local_run_id
        ), ARRAY_A);
        $todos = array();
        foreach ((array) $todo_rows as $todo_row) {
            $decoded = json_decode((string) ($todo_row['todo_json'] ?? '{}'), true);
            if (is_array($decoded)) {
                $todos[] = $decoded;
            }
        }

        return array(
            'ok' => true,
            'local_run_id' => $local_run_id,
            'items' => $items,
            'todo_rows' => $todos,
            'unknown_rows' => array_values(array_filter($items, function($item) {
                return is_array($item) && (string) ($item['content_type'] ?? '') === 'unknown';
            })),
        );
    }

    public function handle_sitemap_competition_get_overview() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $payload = $this->aysa_sitemap_competition_build_payload();
        $result = $this->aysa_broker_request('POST', '/v1/research/sitemap-competition/overview', $payload, 60);
        if (is_wp_error($result)) {
            $fallback = $this->aysa_sitemap_competition_get_local_overview($payload['project_id'], $payload['target_host']);
            if (!empty($fallback)) {
                wp_send_json_success($fallback);
                return;
            }
            wp_send_json_error('Sitemap competition overview failed: ' . $result->get_error_message());
            return;
        }

        $local = $this->aysa_sitemap_competition_get_local_overview($payload['project_id'], $payload['target_host']);
        if (!empty($local)) {
            $result['local'] = $local;
        }
        wp_send_json_success($result);
    }

    public function handle_sitemap_competition_start_scan() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $payload = $this->aysa_sitemap_competition_build_payload();
        $job = $this->aysa_project_jobs_enqueue(
            intval($payload['project_id'] ?? 0),
            'research',
            'sitemap_competition_scan',
            array(
                'project_id' => intval($payload['project_id'] ?? 0),
                'target_host' => (string) ($payload['target_host'] ?? ''),
                'competitors' => isset($payload['competitors']) && is_array($payload['competitors']) ? array_values($payload['competitors']) : array(),
            ),
            array(
                'dedupe_key' => md5(wp_json_encode(array(
                    'project_id' => intval($payload['project_id'] ?? 0),
                    'target_host' => (string) ($payload['target_host'] ?? ''),
                    'competitors' => isset($payload['competitors']) && is_array($payload['competitors']) ? array_values($payload['competitors']) : array(),
                ))),
                'input_summary' => array(
                    'target_host' => (string) ($payload['target_host'] ?? ''),
                    'competitors_count' => isset($payload['competitors']) && is_array($payload['competitors']) ? count($payload['competitors']) : 0,
                ),
            )
        );
        if (is_wp_error($job)) {
            wp_send_json_error($job->get_error_message());
            return;
        }

        $job_row = isset($job['job']) && is_array($job['job']) ? $job['job'] : array();
        $local_run_id = 0;
        if (!empty($job_row['worker_ref'])) {
            $parts = explode(':', (string) $job_row['worker_ref']);
            $local_run_id = intval(end($parts));
        }
        $local = $this->aysa_sitemap_competition_get_local_overview(intval($payload['project_id'] ?? 0), (string) ($payload['target_host'] ?? ''));
        wp_send_json_success(array(
            'ok' => true,
            'queued' => (string) ($job_row['status'] ?? '') === 'queued',
            'local_run_id' => $local_run_id,
            'local' => $local,
            'project_job_rack' => isset($job['rack']) && is_array($job['rack']) ? $job['rack'] : array(),
        ));
    }

    public function handle_sitemap_competition_get_status() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $payload = $this->aysa_sitemap_competition_build_payload();
        $local = $this->aysa_sitemap_competition_get_local_overview($payload['project_id'], $payload['target_host']);
        $local = $this->aysa_sitemap_competition_maybe_recover_local_run($payload['project_id'], $payload['target_host'], $local);

        if (!empty($local)) {
            $local['project_job_rack'] = $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state(intval($payload['project_id'] ?? 0)));
            wp_send_json_success($local);
            return;
        }
        $job = $this->aysa_project_jobs_find_latest_job(intval($payload['project_id'] ?? 0), 'research', 'sitemap_competition_scan', array('queued', 'running', 'completed', 'failed'));
        if (is_array($job)) {
            wp_send_json_success(array(
                'ok' => true,
                'project_id' => intval($payload['project_id']),
                'target_host' => (string) $payload['target_host'],
                'status' => (string) ($job['status'] ?? 'idle'),
                'run' => array(
                    'local_run_id' => 0,
                    'status' => (string) ($job['status'] ?? 'idle'),
                    'remote_run_id' => 0,
                    'overview' => array(),
                ),
                'project_job_rack' => $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state(intval($payload['project_id'] ?? 0))),
            ));
            return;
        }
        wp_send_json_success(array(
            'ok' => true,
            'project_id' => intval($payload['project_id']),
            'target_host' => (string) $payload['target_host'],
            'status' => 'idle',
            'run' => null,
            'project_job_rack' => $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state(intval($payload['project_id'] ?? 0))),
        ));
    }

    public function handle_sitemap_competition_get_detail() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $payload = $this->aysa_sitemap_competition_build_payload();
        $local_run_id = intval(wp_unslash($_POST['local_run_id'] ?? 0));
        $remote_run_id = intval(wp_unslash($_POST['remote_run_id'] ?? 0));
        $broker_payload = $payload;
        if ($remote_run_id > 0) {
            $broker_payload['run_id'] = $remote_run_id;
        }
        $result = $this->aysa_broker_request('POST', '/v1/research/sitemap-competition/detail', $broker_payload, 120);
        if (!is_wp_error($result) && is_array($result) && isset($result['run'])) {
            $local = $this->aysa_sitemap_competition_get_local_detail($payload['project_id'], $payload['target_host'], $local_run_id);
            if (!empty($local)) {
                $result['local'] = $local;
            }
            wp_send_json_success($result);
            return;
        }

        $local = $this->aysa_sitemap_competition_get_local_detail($payload['project_id'], $payload['target_host'], $local_run_id);
        if (!empty($local)) {
            wp_send_json_success($local);
            return;
        }

        wp_send_json_error('Sitemap competition detail failed.');
    }

    public function handle_sitemap_competition_get_history() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $payload = $this->aysa_sitemap_competition_build_payload();
        $result = $this->aysa_broker_request('POST', '/v1/research/sitemap-competition/history', $payload, 60);
        if (is_wp_error($result)) {
            $local = $this->aysa_sitemap_competition_get_local_overview($payload['project_id'], $payload['target_host']);
            if (!empty($local)) {
                wp_send_json_success(array('history' => $local['history']));
                return;
            }
            wp_send_json_error('Sitemap competition history failed: ' . $result->get_error_message());
            return;
        }

        $local = $this->aysa_sitemap_competition_get_local_overview($payload['project_id'], $payload['target_host']);
        if (!empty($local)) {
            $result['local_history'] = $local['history'];
        }
        wp_send_json_success($result);
    }

    public function run_sitemap_competition_scan_event($payload) {
        if (!is_array($payload)) {
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval($payload['project_id'] ?? 0));
        $target_host = strtolower(trim((string) ($payload['target_host'] ?? '')));
        $bound_target_host = $this->aysa_monitoring_extract_host((string) home_url('/'));
        $local_run_id = intval($payload['local_run_id'] ?? 0);
        $attempt = intval($payload['attempt'] ?? 1);
        $phase = (string) ($payload['phase'] ?? 'dispatch');
        if (
            $project_id <= 0
            || $target_host === ''
            || $bound_target_host === ''
            || !hash_equals($bound_target_host, $target_host)
            || $local_run_id <= 0
        ) {
            return;
        }

        $broker_payload = array(
            'project_id' => $project_id,
            'target_host' => $target_host,
            'competitors' => isset($payload['competitors']) && is_array($payload['competitors']) ? array_values($payload['competitors']) : array(),
        );
        if ($phase === 'dispatch') {
            $this->aysa_sitemap_competition_update_local_run($local_run_id, array(
                'status' => 'queued',
                'error' => '',
                'phase' => 'dispatch',
                'attempt' => $attempt,
                'progress_label' => 'Starting broker run',
            ));
            $start = $this->aysa_broker_request('POST', '/v1/research/sitemap-competition/start', $broker_payload, 45);
            if (is_wp_error($start) || !is_array($start) || empty($start['run'])) {
                if ($attempt < 3) {
                    $this->aysa_sitemap_competition_update_local_run($local_run_id, array(
                        'status' => 'queued',
                        'error' => '',
                        'phase' => 'dispatch',
                        'attempt' => $attempt,
                        'last_worker_error' => is_wp_error($start) ? $start->get_error_message() : 'Broker did not create a sitemap competition run.',
                        'progress_label' => 'Retrying broker handoff',
                    ));
                    $retry_payload = $payload;
                    $retry_payload['attempt'] = $attempt + 1;
                    $this->aysa_sitemap_competition_schedule_scan_event($retry_payload, 10);
                    return;
                }
                $this->aysa_sitemap_competition_update_local_run($local_run_id, array(
                    'status' => 'failed',
                    'error' => is_wp_error($start) ? $start->get_error_message() : 'Broker did not create a sitemap competition run.',
                    'phase' => 'dispatch',
                    'attempt' => $attempt,
                ));
                $this->aysa_project_jobs_finalize(
                    $project_id,
                    $this->aysa_project_jobs_make_worker_ref('sitemap_competition', 'sitemap_competition_scan', (string) $local_run_id),
                    'failed',
                    null,
                    is_wp_error($start) ? $start->get_error_message() : 'Broker did not create a sitemap competition run.'
                );
                return;
            }

            $local = $this->aysa_sitemap_competition_persist_broker_result($project_id, $target_host, $start, $local_run_id);
            $remote_run_id = 0;
            if (is_array($local) && isset($local['run']) && is_array($local['run'])) {
                $remote_run_id = intval($local['run']['remote_run_id'] ?? 0);
            }
            if ($remote_run_id <= 0 && is_array($start['run'] ?? null)) {
                $remote_run_id = intval($start['run']['run_id'] ?? 0);
            }
            if ($remote_run_id <= 0) {
                if ($attempt < 3) {
                    $this->aysa_sitemap_competition_update_local_run($local_run_id, array(
                        'status' => 'queued',
                        'error' => '',
                        'phase' => 'dispatch',
                        'attempt' => $attempt,
                        'last_worker_error' => 'Broker did not return remote_run_id.',
                        'progress_label' => 'Retrying broker handoff',
                    ));
                    $retry_payload = $payload;
                    $retry_payload['attempt'] = $attempt + 1;
                    $this->aysa_sitemap_competition_schedule_scan_event($retry_payload, 10);
                    return;
                }
                $this->aysa_sitemap_competition_update_local_run($local_run_id, array(
                    'status' => 'failed',
                    'error' => 'Broker did not return remote_run_id.',
                    'phase' => 'dispatch',
                    'attempt' => $attempt,
                ));
                $this->aysa_project_jobs_finalize(
                    $project_id,
                    $this->aysa_project_jobs_make_worker_ref('sitemap_competition', 'sitemap_competition_scan', (string) $local_run_id),
                    'failed',
                    null,
                    'Broker did not return remote_run_id.'
                );
                return;
            }

            $retry_payload = $payload;
            $retry_payload['phase'] = 'advance';
            $retry_payload['remote_run_id'] = $remote_run_id;
            $retry_payload['attempt'] = 1;
            $this->aysa_sitemap_competition_update_local_run($local_run_id, array(
                'status' => 'running',
                'error' => '',
                'phase' => 'advance',
                'attempt' => 1,
                'remote_run_id' => $remote_run_id,
                'progress_label' => 'Broker run started',
            ));
            $this->aysa_sitemap_competition_schedule_scan_event($retry_payload, 5);
            return;
        }

        $remote_run_id = intval($payload['remote_run_id'] ?? 0);
        if ($remote_run_id <= 0) {
            $local = $this->aysa_sitemap_competition_get_local_overview($project_id, $target_host);
            if (is_array($local) && isset($local['run']) && is_array($local['run'])) {
                $remote_run_id = intval($local['run']['remote_run_id'] ?? 0);
            }
        }
        if ($remote_run_id <= 0) {
            $this->aysa_sitemap_competition_update_local_run($local_run_id, array(
                'status' => 'failed',
                'error' => 'Missing remote_run_id for sitemap competition worker.',
                'phase' => 'advance',
                'attempt' => $attempt,
            ));
            $this->aysa_project_jobs_finalize(
                $project_id,
                $this->aysa_project_jobs_make_worker_ref('sitemap_competition', 'sitemap_competition_scan', (string) $local_run_id),
                'failed',
                null,
                'Missing remote_run_id for sitemap competition worker.'
            );
            return;
        }

        $this->aysa_sitemap_competition_update_local_run($local_run_id, array(
            'status' => 'running',
            'error' => '',
            'phase' => 'advance',
            'attempt' => $attempt,
            'remote_run_id' => $remote_run_id,
            'progress_label' => 'Advancing broker run',
        ));
        $advance = $this->aysa_broker_request('POST', '/v1/research/sitemap-competition/advance', array_merge($broker_payload, array(
            'run_id' => $remote_run_id,
        )), 180);
        if (is_wp_error($advance) || !is_array($advance) || empty($advance['run'])) {
            if ($attempt < 6) {
                $this->aysa_sitemap_competition_update_local_run($local_run_id, array(
                    'status' => 'running',
                    'error' => '',
                    'phase' => 'advance',
                    'attempt' => $attempt,
                    'remote_run_id' => $remote_run_id,
                    'last_worker_error' => is_wp_error($advance) ? $advance->get_error_message() : 'Broker advance request failed.',
                    'progress_label' => 'Retrying broker advance',
                ));
                $retry_payload = $payload;
                $retry_payload['phase'] = 'advance';
                $retry_payload['remote_run_id'] = $remote_run_id;
                $retry_payload['attempt'] = $attempt + 1;
                $this->aysa_sitemap_competition_schedule_scan_event($retry_payload, 20);
                return;
            }
            $this->aysa_sitemap_competition_update_local_run($local_run_id, array(
                'status' => 'failed',
                'error' => is_wp_error($advance) ? $advance->get_error_message() : 'Broker advance request failed.',
                'phase' => 'advance',
                'attempt' => $attempt,
                'remote_run_id' => $remote_run_id,
            ));
            $this->aysa_project_jobs_finalize(
                $project_id,
                $this->aysa_project_jobs_make_worker_ref('sitemap_competition', 'sitemap_competition_scan', (string) $local_run_id),
                'failed',
                null,
                is_wp_error($advance) ? $advance->get_error_message() : 'Broker advance request failed.'
            );
            return;
        }

        $local = $this->aysa_sitemap_competition_persist_broker_result($project_id, $target_host, $advance, $local_run_id);
        $run_status = '';
        if (is_array($local) && isset($local['run']) && is_array($local['run'])) {
            $run_status = (string) ($local['run']['status'] ?? '');
        }
        if ($run_status === 'running' && $attempt < 12) {
            $this->aysa_sitemap_competition_update_local_run($local_run_id, array(
                'status' => 'running',
                'error' => '',
                'phase' => 'advance',
                'attempt' => $attempt,
                'remote_run_id' => $remote_run_id,
                'progress_label' => 'Broker run still processing',
            ));
            $retry_payload = $payload;
            $retry_payload['phase'] = 'advance';
            $retry_payload['remote_run_id'] = $remote_run_id;
            $retry_payload['attempt'] = $attempt + 1;
            $this->aysa_sitemap_competition_schedule_scan_event($retry_payload, 20);
            return;
        }
        if ($run_status === '') {
            $run_status = 'completed';
        }
        $this->aysa_sitemap_competition_update_local_run($local_run_id, array(
            'status' => $run_status,
            'error' => '',
            'phase' => 'completed',
            'attempt' => $attempt,
            'remote_run_id' => $remote_run_id,
            'progress_label' => ucfirst($run_status),
        ));
        if (in_array($run_status, array('completed', 'failed'), true)) {
            $this->aysa_project_jobs_finalize(
                $project_id,
                $this->aysa_project_jobs_make_worker_ref('sitemap_competition', 'sitemap_competition_scan', (string) $local_run_id),
                $run_status === 'failed' ? 'failed' : 'completed',
                array(
                    'local_run_id' => $local_run_id,
                    'remote_run_id' => $remote_run_id,
                    'status' => $run_status,
                ),
                $run_status === 'failed' ? (string) ($local['run']['error'] ?? '') : ''
            );
        }
    }

    public function run_monitoring_ai_refresh_event($payload) {
        if (!is_array($payload)) {
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval($payload['project_id'] ?? 0));
        $domain = strtolower(trim((string) ($payload['domain'] ?? '')));
        $bound_domain = $this->aysa_monitoring_extract_host((string) home_url('/'));
        if (
            $project_id <= 0
            || $domain === ''
            || $bound_domain === ''
            || !hash_equals($bound_domain, $domain)
        ) {
            return;
        }

        $payload['advance'] = 1;
        $status_key = $this->aysa_monitoring_ai_refresh_status_option_key($project_id, $domain);
        $result = $this->aysa_broker_request('POST', '/v1/monitoring/ai/refresh/status', $payload, 60);

        if (is_wp_error($result) || !is_array($result)) {
            $status = get_option($status_key, array());
            if (!is_array($status)) {
                $status = array();
            }
            $status['ok'] = false;
            $status['project_id'] = $project_id;
            $status['domain'] = $domain;
            $status['status'] = (($status['status'] ?? '') === 'completed') ? 'completed' : 'running';
            $status['error'] = is_wp_error($result) ? $result->get_error_message() : 'Unknown AI refresh worker error.';
            $status['message'] = 'AI refresh worker will retry after a transient error.';
            $status['updated_at'] = gmdate('c');
            update_option($status_key, $status, false);
            $this->aysa_monitoring_schedule_ai_refresh_event($payload, 20);
            return;
        }

        $existing = get_option($status_key, array());
        if (!is_array($existing)) {
            $existing = array();
        }
        $status = $this->aysa_monitoring_ai_build_status_from_broker_result($project_id, $domain, $result, $existing);
        update_option($status_key, $status, false);

        if (($status['status'] ?? '') === 'running') {
            $this->aysa_monitoring_schedule_ai_refresh_event($payload, 15);
        }
    }

    private function aysa_monitoring_ai_build_status_from_broker_result($project_id, $domain, $result, $existing = array()) {
        $snapshot = isset($result['snapshot']) && is_array($result['snapshot']) ? $result['snapshot'] : array();
        if (!empty($snapshot)) {
            $this->aysa_monitoring_ai_persist_snapshot_summary($project_id, $domain, $snapshot);
        }

        if (!is_array($existing)) {
            $existing = array();
        }
        $billed_provider_milli_cents = intval($existing['billed_provider_milli_cents'] ?? 0);
        $snapshot_cost_usd = 0.0;
        if (isset($snapshot['summary']) && is_array($snapshot['summary'])) {
            $snapshot_cost_usd = floatval($snapshot['summary']['provider_cost_usd'] ?? 0);
        }
        $provider_milli = $this->aysa_cost_usd_to_milli_cents($snapshot_cost_usd);
        $provider_delta_milli = max(0, $provider_milli - $billed_provider_milli_cents);
        $billed_cents = intval(ceil(($provider_delta_milli * 5) / 1000));
        if ($billed_cents > 0) {
            $this->aysa_wallet_track_server_usage(
                intval($project_id),
                '/v1/monitoring/ai/refresh/status',
                $billed_cents,
                'monitoring_ai_refresh_background',
                array(
                    'module' => 'monitoring_ai',
                    'source' => 'background_refresh',
                    'snapshot_id' => intval($result['snapshot_id'] ?? 0),
                    'provider_cost_milli_cents' => $provider_delta_milli,
                    'pricing_multiplier' => 5,
                )
            );
            $billed_provider_milli_cents = $provider_milli;
        }

        $status = array(
            'ok' => true,
            'project_id' => intval($project_id),
            'domain' => (string) $domain,
            'status' => (string) ($result['status'] ?? 'idle'),
            'message' => (string) ($result['message'] ?? 'No AI refresh in progress.'),
            'updated_at' => gmdate('c'),
            'snapshot_id' => intval($result['snapshot_id'] ?? 0),
            'error' => '',
            'processed_questions' => intval($result['processed_questions'] ?? 0),
            'total_questions' => intval($result['total_questions'] ?? 0),
            'processed_sources' => intval($result['processed_sources'] ?? 0),
            'total_sources' => intval($result['total_sources'] ?? 0),
            'partial_errors' => isset($result['partial_errors']) && is_array($result['partial_errors']) ? array_values($result['partial_errors']) : array(),
            'query_pack' => isset($result['query_pack']) && is_array($result['query_pack']) ? array_values($result['query_pack']) : array(),
            'overview_questions' => isset($result['overview_questions']) && is_array($result['overview_questions']) ? array_values($result['overview_questions']) : array(),
            'snapshot' => $snapshot,
            'history' => isset($result['history']) && is_array($result['history']) ? array_values($result['history']) : array(),
            'billed_provider_milli_cents' => $billed_provider_milli_cents,
        );

        return $this->aysa_monitoring_ai_finalize_status_payload($status, $existing);
    }

    private function aysa_monitoring_ai_finalize_status_payload($status, $existing = array()) {
        if (!is_array($status)) {
            $status = array();
        }
        if (!is_array($existing)) {
            $existing = array();
        }

        $signature = implode('|', array(
            intval($status['snapshot_id'] ?? 0),
            intval($status['processed_questions'] ?? 0),
            intval($status['processed_sources'] ?? 0),
            trim((string) ($status['snapshot']['updated_at'] ?? '')),
        ));
        $existing_signature = (string) ($existing['progress_signature'] ?? '');
        $last_progress_at = (string) ($existing['last_progress_at'] ?? '');
        if ($signature === '' || $signature !== $existing_signature) {
            $last_progress_at = gmdate('c');
        } elseif ($last_progress_at === '') {
            $last_progress_at = gmdate('c');
        }

        $status['progress_signature'] = $signature;
        $status['last_progress_at'] = $last_progress_at;

        return $status;
    }

    private function aysa_monitoring_ai_refresh_needs_rescue($status, $existing = array()) {
        if (!is_array($status) || !is_array($existing)) {
            return false;
        }
        if ((string) ($status['status'] ?? '') !== 'running') {
            return false;
        }
        if (intval($status['snapshot_id'] ?? 0) <= 0) {
            return false;
        }
        if (intval($existing['snapshot_id'] ?? 0) !== intval($status['snapshot_id'] ?? 0)) {
            return false;
        }
        if ((string) ($existing['progress_signature'] ?? '') !== (string) ($status['progress_signature'] ?? '')) {
            return false;
        }

        $last_progress_at = trim((string) ($status['last_progress_at'] ?? ''));
        if ($last_progress_at === '') {
            return false;
        }
        $last_progress_ts = strtotime($last_progress_at);
        if ($last_progress_ts === false) {
            return false;
        }

        $rescue_cooldown_at = trim((string) ($existing['last_rescue_at'] ?? ''));
        if ($rescue_cooldown_at !== '') {
            $rescue_cooldown_ts = strtotime($rescue_cooldown_at);
            if ($rescue_cooldown_ts !== false && $rescue_cooldown_ts > (time() - 45)) {
                return false;
            }
        }

        return $last_progress_ts < (time() - 45);
    }

    private function aysa_monitoring_schedule_ai_refresh_event($payload, $delay_seconds = 1) {
        if (!is_array($payload)) {
            return;
        }

        $project_id = intval($payload['project_id'] ?? 0);
        $domain = strtolower(trim((string) ($payload['domain'] ?? '')));
        if ($project_id <= 0 || $domain === '') {
            return;
        }

        $event_payload = array(
            'project_id' => $project_id,
            'domain' => $domain,
            'target' => (string) ($payload['target'] ?? $domain),
            'brand' => (string) ($payload['brand'] ?? ''),
            'brand_name' => (string) ($payload['brand_name'] ?? ''),
            'company_name' => (string) ($payload['company_name'] ?? ''),
            'industry' => (string) ($payload['industry'] ?? ''),
            'website_type' => (string) ($payload['website_type'] ?? ''),
            'location_code' => intval($payload['location_code'] ?? 2840),
            'language_code' => (string) ($payload['language_code'] ?? 'en'),
            'country_iso_code' => (string) ($payload['country_iso_code'] ?? 'US'),
            'seed_keywords' => isset($payload['seed_keywords']) && is_array($payload['seed_keywords']) ? array_values($payload['seed_keywords']) : array(),
        );
        if (isset($payload['query_pack']) && is_array($payload['query_pack'])) {
            $event_payload['query_pack'] = array_values($payload['query_pack']);
        }

        if (!wp_next_scheduled('aysa_monitoring_ai_refresh_event', array($event_payload))) {
            wp_schedule_single_event(time() + max(1, intval($delay_seconds)), 'aysa_monitoring_ai_refresh_event', array($event_payload));
        }
        if (function_exists('spawn_cron')) {
            spawn_cron();
        }
    }

    private function aysa_monitoring_ai_refresh_status_option_key($project_id, $domain) {
        $project_id = intval($project_id);
        $domain = strtolower(trim((string) $domain));
        return 'aysa_monitoring_ai_refresh_' . $project_id . '_' . md5($domain);
    }

    private function aysa_monitoring_read_only_contract($reason = '') {
        return array(
            'read_only' => true,
            'allow_provider_calls' => false,
            'allow_persistence' => false,
            'allow_worker_advance' => false,
            'allow_scheduler' => false,
            'allow_billing' => false,
            'allow_research_run_create' => false,
            'allow_snapshot_write' => false,
            'allow_alert_send' => false,
            'allow_remediation' => false,
            'reason' => sanitize_key((string) $reason),
        );
    }

    private function aysa_monitoring_refresh_requires_approval_payload($kind) {
        return array(
            'code' => 'monitoring_refresh_requires_explicit_cost_approval',
            'message' => 'Monitoring refresh requires explicit cost-cap approval before any provider call, worker run, scheduler event, persistence or billing.',
            'kind' => sanitize_key((string) $kind),
            'read_only_contract' => $this->aysa_monitoring_read_only_contract('refresh_blocked_until_cost_cap_approval'),
            'next_required_action' => 'MON73_cost_cap_approval_contract',
        );
    }

    private function aysa_monitoring_block_refresh_until_approval($kind) {
        wp_send_json_error($this->aysa_monitoring_refresh_requires_approval_payload($kind));
        return true;
    }

    private function aysa_monitoring_build_ai_payload() {
        $project_id = intval($this->aysa_resolve_project_id_with_local_fallback());
        if ($project_id <= 0) {
            return array(
                'project_id' => 0,
                'domain' => '',
            );
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $context = $this->aysa_monitoring_resolve_context((string) wp_unslash($_POST['target'] ?? ''));
        $user_id = get_current_user_id();
        $general = get_option('aysa_wizard_general_' . intval($user_id), array());
        if (!is_array($general)) {
            $general = array();
        }
        $seed_data = $this->aysa_keyword_strategy_extract_seed_data($user_id);
        $brand = trim((string) ($general['brand_name'] ?? $general['brand'] ?? get_option('aysa_brand_name', '')));
        $company_name = trim((string) ($general['company_name'] ?? get_option('aysa_company_name', get_bloginfo('name'))));
        if ($brand === '') {
            $brand = $company_name !== '' ? $company_name : $context['target'];
        }
        $industry = trim((string) ($general['industry'] ?? get_option('aysa_industry', '')));
        $website_type = trim((string) ($general['website_type'] ?? get_option('aysa_website_type', '')));
        $country_iso = $this->aysa_monitoring_country_iso_from_location_code(intval($context['location_code'] ?? 2840));
        $query_pack = array();
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        if (isset($_POST['query_pack'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
            $decoded_query_pack = json_decode((string) wp_unslash($_POST['query_pack']), true);
            if (is_array($decoded_query_pack)) {
                $query_pack = $decoded_query_pack;
            }
        }

        $payload = array(
            'project_id' => $project_id,
            'domain' => (string) ($context['target'] ?? ''),
            'target' => (string) ($context['target'] ?? ''),
            'brand' => $brand,
            'brand_name' => $brand,
            'company_name' => $company_name,
            'industry' => $industry,
            'website_type' => $website_type,
            'location_code' => intval($context['location_code'] ?? 2840),
            'language_code' => strtolower((string) ($context['language_code'] ?? 'en')),
            'country_iso_code' => $country_iso,
            'seed_keywords' => isset($seed_data['seed_keywords']) && is_array($seed_data['seed_keywords'])
                ? array_values($seed_data['seed_keywords'])
                : array(),
        );
        if (!empty($query_pack)) {
            $payload['query_pack'] = $query_pack;
        }
        return $payload;
    }

    private function aysa_monitoring_country_iso_from_location_code($location_code) {
        $location_code = intval($location_code);
        return match ($location_code) {
            2642 => 'RO',
            2276 => 'DE',
            2250 => 'FR',
            2380 => 'IT',
            2724 => 'ES',
            2826 => 'GB',
            default => 'US',
        };
    }

    public function handle_monitoring_rank_latest_from_db() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        global $wpdb;
        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $context = $this->aysa_monitoring_resolve_context((string) wp_unslash($_POST['target'] ?? ''));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $associations_raw = isset($_POST['associations']) ? wp_unslash($_POST['associations']) : '';
        $associations = json_decode((string) $associations_raw, true);
        if (!is_array($associations)) {
            $associations = array();
        }
        $table = esc_sql($this->aysa_monitoring_history_table());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table));
        if ($table_exists !== $table) {
            wp_send_json_success(array(
                'rows' => array(),
                'source' => 'db',
                'count' => 0,
                'snapshot_state' => !empty($associations) ? 'tracked_but_not_refreshed' : 'rank_snapshot_missing',
                'message' => 'Nu există încă istoric local de poziții pentru Monitoring.',
                'read_only_contract' => $this->aysa_monitoring_read_only_contract('rank_history_table_missing'),
            ));
            return;
        }

        $query_latest_rows = function() use ($wpdb, $table, $project_id, $context) {
            $sql = "
            SELECT h.*
            FROM {$table} h
            INNER JOIN (
                SELECT MAX(id) AS max_id
                FROM {$table}
                WHERE project_id = %d
                  AND target_host = %s
                  AND (
                        (position IS NOT NULL AND position > 0)
                     OR (position_absolute IS NOT NULL AND position_absolute > 0)
                     OR (ctr IS NOT NULL AND ctr > 0)
                     OR (search_volume IS NOT NULL AND search_volume > 0)
                     OR (bounce_rate IS NOT NULL AND bounce_rate > 0)
                     OR (sessions IS NOT NULL AND sessions > 0)
                     OR (gsc_clicks IS NOT NULL AND gsc_clicks > 0)
                     OR (gsc_impressions IS NOT NULL AND gsc_impressions > 0)
                     OR (intent IS NOT NULL AND intent <> '')
                     OR (traffic IS NOT NULL AND traffic > 0)
                     OR (traffic_pct IS NOT NULL AND traffic_pct > 0)
                     OR (kd IS NOT NULL AND kd > 0)
                     OR (target_url IS NOT NULL AND target_url <> '')
                     OR (item_types IS NOT NULL AND item_types <> '')
                     OR (check_url IS NOT NULL AND check_url <> '')
                     OR (matched_item_type IS NOT NULL AND matched_item_type <> '')
                  )
                GROUP BY keyword, associated_on, id_intern
            ) latest ON latest.max_id = h.id
            ORDER BY h.keyword ASC
        ";
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query: table identifiers and structure are internal, and request-derived values are prepared before execution. Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            return $wpdb->get_results($wpdb->prepare($sql, $project_id, $context['target']), ARRAY_A);
        };

        $rows = $query_latest_rows();
        if (!is_array($rows)) {
            $rows = array();
        }

        $out = array();
        foreach ($rows as $row) {
            $latest_pos = is_null($row['position']) ? null : floatval($row['position']);
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $prev_pos = $wpdb->get_var($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT position
" .
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
                "                 FROM {$table}
                 WHERE project_id = %d
                   AND target_host = %s
                   AND keyword = %s
                   AND associated_on = %s
                   AND id_intern = %d
                 ORDER BY id DESC
                 LIMIT 1 OFFSET 1",
                $project_id,
                $context['target'],
                (string) $row['keyword'],
                (string) $row['associated_on'],
                intval($row['id_intern'])
            ));
            $prev_pos = is_null($prev_pos) ? null : floatval($prev_pos);
            $delta = null;
            if (!is_null($latest_pos) && !is_null($prev_pos) && $prev_pos > 0) {
                $delta = $prev_pos - $latest_pos;
            }
	            $out[] = array(
	                'id' => intval($row['id']),
	                'keyword' => (string) $row['keyword'],
	                'associated_on' => (string) $row['associated_on'],
	                'id_intern' => intval($row['id_intern']),
	                'link' => (string) $row['target_url'],
	                'rank_group' => $latest_pos,
	                'rank_absolute' => is_null($row['position_absolute']) ? null : floatval($row['position_absolute']),
	                'position' => $latest_pos,
	                'position_absolute' => is_null($row['position_absolute']) ? null : floatval($row['position_absolute']),
	                'previous_position' => $prev_pos,
	                'position_delta' => $delta,
	                'ctr' => is_null($row['ctr']) ? null : floatval($row['ctr']),
                    'intent' => (string) ($row['intent'] ?? ''),
                    'traffic' => is_null($row['traffic']) ? null : intval($row['traffic']),
                    'traffic_pct' => is_null($row['traffic_pct']) ? null : floatval($row['traffic_pct']),
                    'kd' => is_null($row['kd']) ? null : floatval($row['kd']),
	                'search_volume' => is_null($row['search_volume']) ? null : intval($row['search_volume']),
	                'bounce_rate' => is_null($row['bounce_rate']) ? null : floatval($row['bounce_rate']),
	                'sessions' => is_null($row['sessions']) ? null : intval($row['sessions']),
	                'gsc_clicks' => is_null($row['gsc_clicks']) ? null : intval($row['gsc_clicks']),
	                'gsc_impressions' => is_null($row['gsc_impressions']) ? null : intval($row['gsc_impressions']),
	                'item_types' => (string) ($row['item_types'] ?? ''),
	                'check_url' => (string) ($row['check_url'] ?? ''),
	                'matched_item_type' => (string) ($row['matched_item_type'] ?? ''),
	                'last_checked_at' => (string) $row['tracked_at'],
	            );
        }

        wp_send_json_success(array(
            'rows' => $out,
            'source' => 'db',
            'count' => count($out),
            'tracked_keyword_count' => count($associations),
            'snapshot_state' => count($out) > 0 ? 'stored_rank_snapshot' : (!empty($associations) ? 'tracked_but_not_refreshed' : 'rank_snapshot_missing'),
            'message' => count($out) > 0
                ? 'Stored Monitoring rank snapshot loaded from local history.'
                : (!empty($associations)
                    ? 'Ai keyworduri urmărite, dar nu există încă un snapshot de poziții salvat în siguranță.'
                    : 'Nu există snapshot de poziții salvat pentru acest proiect.'),
            'read_only_contract' => $this->aysa_monitoring_read_only_contract('rank_latest_local_history_only'),
        ));
    }

    public function handle_monitoring_rank_refresh_due() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }
        if ($this->aysa_monitoring_block_refresh_until_approval('rank_refresh_due')) {
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $context = $this->aysa_monitoring_resolve_context((string) wp_unslash($_POST['target'] ?? ''));
        $max_keywords = intval(wp_unslash($_POST['max_keywords'] ?? 30));
        $force = !empty($_POST['force']) ? true : false;
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $associations_raw = isset($_POST['associations']) ? wp_unslash($_POST['associations']) : '';
        $associations = json_decode((string) $associations_raw, true);
        if (!is_array($associations)) {
            $associations = array();
        }

        $payload = array(
            'project_id' => $project_id,
            'target' => $context['target'],
            'se_domain' => $context['se_domain'],
            'location_code' => $context['location_code'],
            'language_code' => $context['language_code'],
            'max_keywords' => max(1, min(100, $max_keywords)),
            'force' => $force,
            'associations' => $associations,
        );

        $result = $this->aysa_broker_request('POST', '/v1/dataforseo/rank-tracking/refresh-due', $payload, 120);
        if (is_wp_error($result)) {
            wp_send_json_error('Rank refresh failed: ' . $result->get_error_message());
            return;
        }

        // Bill only executed provider work; do not charge for DB/cache reads or request bootstrap.
        $provider_milli = $this->aysa_cost_usd_to_milli_cents($result['cost'] ?? 0);
        $billed_cents = intval(ceil(($provider_milli * 5) / 1000));
        if ($billed_cents > 0) {
            $this->aysa_wallet_track_server_usage(
                $project_id,
                '/v1/dataforseo/rank-tracking/refresh-due',
                $billed_cents,
                'monitoring_rank_refresh_due',
                array(
                    'module' => 'monitoring_analytics',
                    'source' => 'manual_refresh',
                    'provider_cost_milli_cents' => $provider_milli,
                    'pricing_multiplier' => 5,
                )
            );
        }

        // First persist directly from refresh response (authoritative execution output).
        $persisted_from_refresh = 0;
        if (is_array($result) && isset($result['results']) && is_array($result['results']) && !empty($result['results'])) {
            $refresh_rows = array();
            foreach ($result['results'] as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $keyword = trim((string) ($row['keyword'] ?? ''));
                if ($keyword === '') {
                    continue;
                }
                $refresh_rows[] = array(
                    'keyword' => $keyword,
                    'associated_on' => (string) ($row['associated_on'] ?? 'other'),
                    'id_intern' => intval($row['id_intern'] ?? 0),
                    'link' => (string) ($row['found_url'] ?? ''),
                    'position' => isset($row['position']) ? $row['position'] : null,
                    'position_absolute' => isset($row['position_absolute']) ? $row['position_absolute'] : null,
                    'status_code' => isset($row['status_code']) ? intval($row['status_code']) : null,
                    'status_message' => (string) ($row['status_message'] ?? ''),
                    'item_types' => isset($row['item_types']) ? $row['item_types'] : array(),
                    'check_url' => (string) ($row['check_url'] ?? ''),
                    'matched_item_type' => (string) ($row['matched_item_type'] ?? ''),
                    'last_checked_at' => (string) ($row['checked_at'] ?? ''),
                );
            }
            if (!empty($refresh_rows)) {
                $persist_info = $this->aysa_monitoring_persist_rows($project_id, $context['target'], $refresh_rows);
                $persisted_from_refresh = intval($persist_info['inserted'] ?? 0);
                $result['persist_debug_refresh'] = $persist_info;
            }
        }

        // Persist updated rank rows locally after manual refresh (or cron equivalent),
        // so regular page loads can work strictly from local DB cache.
        $plan_payload = array(
            'project_id' => $project_id,
            'target' => $context['target'],
            'se_domain' => $context['se_domain'],
            'location_code' => $context['location_code'],
            'language_code' => $context['language_code'],
            'associations' => $associations,
        );
        $plan_result = $this->aysa_broker_request('POST', '/v1/dataforseo/rank-tracking/plan-enriched', $plan_payload, 60);
        if (!is_wp_error($plan_result) && is_array($plan_result) && isset($plan_result['rows']) && is_array($plan_result['rows'])) {
            $persist_plan = $this->aysa_monitoring_persist_rows($project_id, $context['target'], $plan_result['rows']);
            $result['persist_debug_plan'] = $persist_plan;
            $result['persisted_rows'] = max($persisted_from_refresh, intval($persist_plan['inserted'] ?? 0));
        } else {
            $result['persisted_rows'] = $persisted_from_refresh;
        }
        $result['persisted_from_refresh'] = $persisted_from_refresh;

        wp_send_json_success($result);
    }

    private function aysa_monitoring_refresh_status_key($project_id) {
        return 'aysa_monitoring_rank_refresh_status_' . intval($project_id);
    }

    private function aysa_monitoring_refresh_queue_key($project_id) {
        return 'aysa_monitoring_rank_refresh_queue_' . intval($project_id);
    }

    private function aysa_monitoring_refresh_lock_key($project_id) {
        return 'aysa_monitoring_rank_refresh_lock_' . intval($project_id);
    }

    private function aysa_monitoring_get_refresh_status($project_id) {
        $status = get_option($this->aysa_monitoring_refresh_status_key($project_id), array());
        if (!is_array($status)) {
            $status = array();
        }

        return array(
            'run_id' => (string) ($status['run_id'] ?? ''),
            'status' => (string) ($status['status'] ?? 'idle'),
            'total' => intval($status['total'] ?? 0),
            'processed' => intval($status['processed'] ?? 0),
            'refreshed' => intval($status['refreshed'] ?? 0),
            'failed' => intval($status['failed'] ?? 0),
            'deferred' => intval($status['deferred'] ?? 0),
            'last_error' => (string) ($status['last_error'] ?? ''),
            'started_at' => intval($status['started_at'] ?? 0),
            'updated_at' => intval($status['updated_at'] ?? 0),
            'finished_at' => intval($status['finished_at'] ?? 0),
        );
    }

    private function aysa_monitoring_save_refresh_status($project_id, $status) {
        if (!is_array($status)) {
            return;
        }
        update_option($this->aysa_monitoring_refresh_status_key($project_id), $status, false);
    }

    private function aysa_monitoring_clear_refresh_queue($project_id) {
        delete_option($this->aysa_monitoring_refresh_queue_key($project_id));
    }

    private function aysa_monitoring_normalize_associations($associations) {
        $normalized = array();
        if (!is_array($associations)) {
            return $normalized;
        }

        foreach ($associations as $assoc) {
            if (!is_array($assoc)) {
                continue;
            }
            $keyword = trim((string) ($assoc['keyword'] ?? ''));
            if ($keyword === '') {
                continue;
            }
            $normalized[] = array(
                'keyword' => $keyword,
                'associated_on' => (string) ($assoc['associated_on'] ?? 'other'),
                'id_intern' => intval($assoc['id_intern'] ?? 0),
                'link' => (string) ($assoc['link'] ?? ''),
            );
        }

        return $normalized;
    }

    private function aysa_monitoring_schedule_background_refresh($project_id, $delay_seconds = 1) {
        $timestamp = time() + max(1, intval($delay_seconds));
        wp_schedule_single_event($timestamp, 'aysa_monitoring_rank_refresh_background_event', array(intval($project_id)));
        if (function_exists('spawn_cron')) {
            spawn_cron();
        }
    }

    private function aysa_monitoring_ensure_refresh_cron_tick($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return;
        }
        if (!wp_next_scheduled('aysa_monitoring_rank_refresh_cron_tick', array($project_id))) {
            wp_schedule_event(time() + 60, 'aysa_every_minute', 'aysa_monitoring_rank_refresh_cron_tick', array($project_id));
        }
        if (function_exists('spawn_cron')) {
            spawn_cron();
        }
    }

    private function aysa_monitoring_clear_refresh_cron_tick($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return;
        }
        while (false !== ($ts = wp_next_scheduled('aysa_monitoring_rank_refresh_cron_tick', array($project_id)))) {
            wp_unschedule_event($ts, 'aysa_monitoring_rank_refresh_cron_tick', array($project_id));
        }
    }

    private function aysa_monitoring_refresh_batch_and_persist($project_id, $context, $associations) {
        $project_id = intval($project_id);
        $context = is_array($context) ? $context : array();
        $associations = $this->aysa_monitoring_normalize_associations($associations);
        if ($project_id <= 0 || empty($associations)) {
            return array(
                'ok' => false,
                'inserted' => 0,
                'provider_cost_milli_cents' => 0,
                'error' => 'Invalid refresh batch payload.'
            );
        }

        $persisted_from_overview = 0;
        $overview_payload = array(
            'project_id' => $project_id,
            'target' => (string) ($context['target'] ?? ''),
            'se_domain' => (string) ($context['se_domain'] ?? 'google.com'),
            'location_code' => intval($context['location_code'] ?? 2840),
            'language_code' => (string) ($context['language_code'] ?? 'en'),
            'max_keywords' => count($associations),
            'include_serp_info' => true,
            'associations' => $associations,
        );
        $overview_result = $this->aysa_broker_request('POST', '/v1/monitoring/keywords/overview', $overview_payload, 120);
        if (!is_wp_error($overview_result)) {
            $overview_rows = $this->aysa_monitoring_extract_overview_rows($overview_result);
            $overview_rows = $this->aysa_monitoring_normalize_overview_rows($overview_rows, $associations);
            if (!empty($overview_rows)) {
                $persist_overview = $this->aysa_monitoring_persist_rows($project_id, (string) ($context['target'] ?? ''), $overview_rows);
                $persisted_from_overview = intval($persist_overview['inserted'] ?? 0);
            }
        }

        $payload = array(
            'project_id' => $project_id,
            'target' => (string) ($context['target'] ?? ''),
            'se_domain' => (string) ($context['se_domain'] ?? 'google.com'),
            'location_code' => intval($context['location_code'] ?? 2840),
            'language_code' => (string) ($context['language_code'] ?? 'en'),
            'max_keywords' => count($associations),
            'force' => true,
            'associations' => $associations,
        );

        $plan_payload = array(
            'project_id' => $project_id,
            'target' => (string) ($context['target'] ?? ''),
            'se_domain' => (string) ($context['se_domain'] ?? 'google.com'),
            'location_code' => intval($context['location_code'] ?? 2840),
            'language_code' => (string) ($context['language_code'] ?? 'en'),
            'associations' => $associations,
        );

        $result = $this->aysa_broker_request('POST', '/v1/dataforseo/rank-tracking/refresh-due', $payload, 120);
        if (is_wp_error($result)) {
            $err_code = (string) $result->get_error_code();
            $is_transient_broker_error = in_array($err_code, array('broker_invalid_response', 'broker_http_failed', 'broker_error'), true);
            // Graceful fallback: keep successful overview persistence instead of marking the entire batch failed.
            if ($persisted_from_overview > 0) {
                return array(
                    'ok' => true,
                    'inserted' => $persisted_from_overview,
                    'queued' => false,
                    'provider_cost_milli_cents' => 0,
                    'error' => '',
                );
            }
            if ($is_transient_broker_error) {
                // Try fallback planning data even when refresh-due fails.
                $plan_result = $this->aysa_broker_request('POST', '/v1/dataforseo/rank-tracking/plan-enriched', $plan_payload, 60);
                if (!is_wp_error($plan_result) && is_array($plan_result) && isset($plan_result['rows']) && is_array($plan_result['rows'])) {
                    $persist_plan = $this->aysa_monitoring_persist_rows($project_id, (string) ($context['target'] ?? ''), $plan_result['rows']);
                    $inserted = intval($persist_plan['inserted'] ?? 0);
                    if ($inserted > 0) {
                        return array(
                            'ok' => true,
                            'inserted' => $inserted,
                            'queued' => false,
                            'provider_cost_milli_cents' => $this->aysa_cost_usd_to_milli_cents($plan_result['cost'] ?? 0),
                            'error' => '',
                        );
                    }
                }
                return array(
                    'ok' => true,
                    'inserted' => 0,
                    'queued' => true,
                    'provider_cost_milli_cents' => 0,
                    'error' => $result->get_error_message(),
                );
            }
            return array(
                'ok' => false,
                'inserted' => $persisted_from_overview,
                'queued' => false,
                'provider_cost_milli_cents' => 0,
                'error' => $result->get_error_message(),
            );
        }

        $persisted_from_refresh = 0;
        $all_queued = false;
        if (is_array($result) && isset($result['results']) && is_array($result['results']) && !empty($result['results'])) {
            $refresh_rows = array();
            $all_queued = true;
            foreach ($result['results'] as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $keyword = trim((string) ($row['keyword'] ?? ''));
                if ($keyword === '') {
                    continue;
                }
                $status_code = isset($row['status_code']) ? intval($row['status_code']) : null;
                if ($status_code !== 40602) {
                    $all_queued = false;
                }
                $refresh_rows[] = array(
                    'keyword' => $keyword,
                    'associated_on' => (string) ($row['associated_on'] ?? 'other'),
                    'id_intern' => intval($row['id_intern'] ?? 0),
                    'link' => (string) ($row['found_url'] ?? ''),
                    'position' => isset($row['position']) ? $row['position'] : null,
                    'position_absolute' => isset($row['position_absolute']) ? $row['position_absolute'] : null,
                    'status_code' => $status_code,
                    'status_message' => (string) ($row['status_message'] ?? ''),
                    'item_types' => isset($row['item_types']) ? $row['item_types'] : array(),
                    'check_url' => (string) ($row['check_url'] ?? ''),
                    'matched_item_type' => (string) ($row['matched_item_type'] ?? ''),
                    'last_checked_at' => (string) ($row['checked_at'] ?? ''),
                );
            }
            if (!empty($refresh_rows)) {
                $persist_info = $this->aysa_monitoring_persist_rows($project_id, (string) ($context['target'] ?? ''), $refresh_rows);
                $persisted_from_refresh = intval($persist_info['inserted'] ?? 0);
            }
        }

        $plan_result = $this->aysa_broker_request('POST', '/v1/dataforseo/rank-tracking/plan-enriched', $plan_payload, 60);
        if (!is_wp_error($plan_result) && is_array($plan_result) && isset($plan_result['rows']) && is_array($plan_result['rows'])) {
            $persist_plan = $this->aysa_monitoring_persist_rows($project_id, (string) ($context['target'] ?? ''), $plan_result['rows']);
            return array(
                'ok' => true,
                'inserted' => max($persisted_from_overview, $persisted_from_refresh, intval($persist_plan['inserted'] ?? 0)),
                'queued' => ($all_queued && max($persisted_from_overview, $persisted_from_refresh, intval($persist_plan['inserted'] ?? 0)) <= 0),
                'provider_cost_milli_cents' => max(
                    $this->aysa_cost_usd_to_milli_cents($result['cost'] ?? 0),
                    $this->aysa_cost_usd_to_milli_cents($plan_result['cost'] ?? 0)
                ),
                'error' => '',
            );
        }

        return array(
            'ok' => true,
            'inserted' => max($persisted_from_overview, $persisted_from_refresh),
            'queued' => ($all_queued && max($persisted_from_overview, $persisted_from_refresh) <= 0),
            'provider_cost_milli_cents' => $this->aysa_cost_usd_to_milli_cents($result['cost'] ?? 0),
            'error' => '',
        );
    }

    public function handle_monitoring_rank_refresh_start_background() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }
        if ($this->aysa_monitoring_block_refresh_until_approval('rank_refresh_start_background')) {
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $associations_raw = isset($_POST['associations']) ? wp_unslash($_POST['associations']) : '';
        $associations = json_decode((string) $associations_raw, true);
        $associations = $this->aysa_monitoring_normalize_associations($associations);
        if (empty($associations)) {
            wp_send_json_error('No tracked keywords provided.');
            return;
        }

        $batch_size = intval(wp_unslash($_POST['batch_size'] ?? 20));
        if ($batch_size < 5) {
            $batch_size = 5;
        } elseif ($batch_size > 10) {
            $batch_size = 10;
        }
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $context = $this->aysa_monitoring_resolve_context((string) wp_unslash($_POST['target'] ?? ''));
        $job = $this->aysa_project_jobs_enqueue(
            $project_id,
            'monitoring',
            'rank_refresh',
            array(
                'project_id' => $project_id,
                'associations' => array_values($associations),
                'batch_size' => $batch_size,
                'context' => $context,
                // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
                'target' => (string) wp_unslash($_POST['target'] ?? ''),
            ),
            array(
                // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
                'dedupe_key' => md5(wp_json_encode(array($project_id, $associations, $batch_size, (string) wp_unslash($_POST['target'] ?? '')))),
                'input_summary' => array(
                    'tracked_keywords' => count($associations),
                ),
            )
        );

        wp_send_json_success(array(
            'started' => in_array((string) ($job['status'] ?? ''), array('queued', 'running', 'completed'), true),
            'run_id' => (string) ($job['job_id'] ?? ''),
            'status' => $this->aysa_monitoring_get_refresh_status($project_id),
            'project_job_status' => array(
                'status' => (string) ($job['status'] ?? 'queued'),
                'worker_ref' => (string) ($job['worker_ref'] ?? ''),
                'worker_status' => (string) ($job['worker_status'] ?? ''),
                'blocked_by' => isset($job['blocked_by']) && is_array($job['blocked_by']) ? array_values($job['blocked_by']) : array(),
            ),
            'project_job_rack' => $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state($project_id)),
        ));
    }

    public function handle_monitoring_rank_refresh_status() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        $status = $this->aysa_monitoring_get_refresh_status($project_id);
        $queue = get_option($this->aysa_monitoring_refresh_queue_key($project_id), array());
        if (!is_array($queue)) {
            $queue = array();
        }

        $next_run = wp_next_scheduled('aysa_monitoring_rank_refresh_background_event', array($project_id));
        $status_is_running = (($status['status'] ?? 'idle') === 'running');
        $updated_at = intval($status['updated_at'] ?? 0);
        $stale_for = ($updated_at > 0) ? (time() - $updated_at) : PHP_INT_MAX;

        if (empty($status)) {
            $job = $this->aysa_project_jobs_find_latest_job($project_id, 'monitoring', 'rank_refresh', array('queued', 'running', 'completed', 'failed'));
            if (is_array($job)) {
                wp_send_json_success(array(
                    'run_id' => (string) ($job['job_id'] ?? ''),
                    'status' => (string) ($job['status'] ?? 'idle'),
                    'worker_ref' => (string) ($job['worker_ref'] ?? ''),
                    'worker_status' => (string) ($job['worker_status'] ?? ''),
                    'project_job_rack' => $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state($project_id)),
                    'status_poll_advanced_worker' => false,
                    'read_only_contract' => $this->aysa_monitoring_read_only_contract('rank_status_project_job_read_only'),
                ));
                return;
            }
        }

        $status['pending'] = max(0, intval($status['total']) - intval($status['processed']));
        $status['queue_cursor'] = intval($queue['cursor'] ?? 0);
        $status['queue_batch_size'] = intval($queue['batch_size'] ?? 0);
        $status['is_running'] = (($status['status'] ?? 'idle') === 'running');
        $status['next_run'] = $next_run;
        $status['status_poll_advanced_worker'] = false;
        $status['read_only_contract'] = $this->aysa_monitoring_read_only_contract('rank_refresh_status_read_only');
        if ($status_is_running && ($next_run === false || $stale_for > 20)) {
            $status['read_only_warning'] = 'Refresh-ul pare în lucru, dar citirea statusului nu avansează workerul. Workerul trebuie să ruleze prin cron/runner explicit.';
            $status['refresh_requires_approval'] = true;
        }
        $status['project_job_rack'] = $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state($project_id));

        wp_send_json_success($status);
    }

    public function handle_monitoring_rank_refresh_background_event($project_id = 0) {
        $project_id = $this->aysa_resolve_installed_project_id(intval($project_id));
        if ($project_id <= 0) {
            return;
        }

        $lock_key = $this->aysa_monitoring_refresh_lock_key($project_id);
        if (get_transient($lock_key)) {
            $this->aysa_monitoring_schedule_background_refresh($project_id, 20);
            return;
        }
        set_transient($lock_key, 1, 180);

        try {
            $queue = get_option($this->aysa_monitoring_refresh_queue_key($project_id), array());
            if (!is_array($queue) || empty($queue['associations']) || !isset($queue['run_id'])) {
                $this->aysa_monitoring_clear_refresh_cron_tick($project_id);
                return;
            }

            $status = $this->aysa_monitoring_get_refresh_status($project_id);
            if (($status['status'] ?? '') !== 'running') {
                $this->aysa_monitoring_clear_refresh_cron_tick($project_id);
                return;
            }

            $cursor = max(0, intval($queue['cursor'] ?? 0));
            $batch_size = intval($queue['batch_size'] ?? 20);
            if ($batch_size < 5) {
                $batch_size = 5;
            } elseif ($batch_size > 10) {
                $batch_size = 10;
            }

            $associations = is_array($queue['associations']) ? $queue['associations'] : array();
            $batch = array_slice($associations, $cursor, $batch_size);

            if (empty($batch)) {
                $status['status'] = 'completed';
                $status['updated_at'] = time();
                $status['finished_at'] = time();
                $this->aysa_monitoring_save_refresh_status($project_id, $status);
                $this->aysa_monitoring_clear_refresh_queue($project_id);
                $this->aysa_monitoring_clear_refresh_cron_tick($project_id);
                $this->aysa_project_jobs_finalize(
                    $project_id,
                    $this->aysa_project_jobs_make_worker_ref('monitoring_rank_refresh', 'rank_refresh', (string) ($status['run_id'] ?? '')),
                    'completed',
                    array(
                        'processed' => intval($status['processed'] ?? 0),
                        'refreshed' => intval($status['refreshed'] ?? 0),
                    ),
                    ''
                );
                return;
            }

            $context = is_array($queue['context'] ?? null)
                ? $queue['context']
                : $this->aysa_monitoring_resolve_context('');
            $result = $this->aysa_monitoring_refresh_batch_and_persist($project_id, $context, $batch);
            if (!empty($result['queued'])) {
                $queue['current_retry'] = intval($queue['current_retry'] ?? 0) + 1;
                $status['updated_at'] = time();
                $status['last_error'] = (string) ($result['error'] ?? '');
                if ($queue['current_retry'] > 5) {
                    $current_batch_size = intval($queue['batch_size'] ?? 20);
                    if ($current_batch_size > 5) {
                        $queue['batch_size'] = max(5, intval(floor($current_batch_size / 2)));
                        $queue['current_retry'] = 0;
                        $status['last_error'] = 'Broker delayed responses; reducing batch size to ' . intval($queue['batch_size']) . '.';
                    } else {
                        $status['deferred'] = intval($status['deferred'] ?? 0) + count($batch);
                        $status['processed'] = min(intval($status['total']), intval($status['processed']) + count($batch));
                        $status['last_error'] = 'Batch stayed queued for too long and was deferred.';
                        $queue['cursor'] = $cursor + count($batch);
                        $queue['current_retry'] = 0;
                    }
                }
                $this->aysa_monitoring_save_refresh_status($project_id, $status);
                update_option($this->aysa_monitoring_refresh_queue_key($project_id), $queue, false);
                $this->aysa_monitoring_schedule_background_refresh($project_id, 20);
                return;
            }

            $status['processed'] = min(intval($status['total']), intval($status['processed']) + count($batch));
            if (!empty($result['ok'])) {
                $status['refreshed'] = intval($status['refreshed']) + intval($result['inserted'] ?? 0);
            } else {
                $status['failed'] = intval($status['failed']) + count($batch);
                $status['last_error'] = (string) ($result['error'] ?? 'Unknown refresh error');
            }
            $status['updated_at'] = time();
            $this->aysa_monitoring_save_refresh_status($project_id, $status);

            $queue['cursor'] = $cursor + count($batch);
            $queue['current_retry'] = 0;
            update_option($this->aysa_monitoring_refresh_queue_key($project_id), $queue, false);
            $provider_milli = max(0, intval($result['provider_cost_milli_cents'] ?? 0));
            $billed_cents = intval(ceil(($provider_milli * 5) / 1000));
            if ($billed_cents > 0) {
                $this->aysa_wallet_track_server_usage(
                    $project_id,
                    '/v1/dataforseo/rank-tracking/refresh-due',
                    $billed_cents,
                    'monitoring_rank_refresh_batch',
                    array(
                        'module' => 'monitoring_analytics',
                        'source' => 'background_refresh',
                        'run_id' => (string) ($queue['run_id'] ?? ''),
                        'batch_count' => count($batch),
                        'cursor_from' => $cursor,
                        'cursor_to' => intval($queue['cursor']),
                        'result' => !empty($result['ok']) ? 'ok' : 'failed',
                        'provider_cost_milli_cents' => $provider_milli,
                        'pricing_multiplier' => 5,
                    )
                );
            }

            if ($queue['cursor'] >= count($associations)) {
                $status['status'] = 'completed';
                $status['updated_at'] = time();
                $status['finished_at'] = time();
                $this->aysa_monitoring_save_refresh_status($project_id, $status);
                $this->aysa_monitoring_clear_refresh_queue($project_id);
                $this->aysa_monitoring_clear_refresh_cron_tick($project_id);
                $this->aysa_project_jobs_finalize(
                    $project_id,
                    $this->aysa_project_jobs_make_worker_ref('monitoring_rank_refresh', 'rank_refresh', (string) ($status['run_id'] ?? '')),
                    'completed',
                    array(
                        'processed' => intval($status['processed'] ?? 0),
                        'refreshed' => intval($status['refreshed'] ?? 0),
                    ),
                    ''
                );
                return;
            }

            $this->aysa_monitoring_schedule_background_refresh($project_id, 15);
        } finally {
            delete_transient($lock_key);
        }
    }

    public function register_aysa_cron_schedules($schedules) {
        if (!is_array($schedules)) {
            $schedules = array();
        }
        if (!isset($schedules['aysa_every_minute'])) {
            $schedules['aysa_every_minute'] = array(
                'interval' => 60,
                'display' => 'AYSA Every Minute',
            );
        }
        if (!isset($schedules['aysa_every_fifteen_minutes'])) {
            $schedules['aysa_every_fifteen_minutes'] = array(
                'interval' => 15 * 60,
                'display' => 'AYSA Every Fifteen Minutes',
            );
        }
        return $schedules;
    }

public function aysa_monitoring_history_maybe_create_table() {
        // Always run dbDelta to keep schema in sync when columns are added.
        $this->aysa_monitoring_history_create_table();
    }

    public function aysa_monitoring_ai_history_maybe_create_table() {
        $this->aysa_monitoring_ai_history_create_table();
    }

    private function aysa_monitoring_ai_persist_snapshot_summary($project_id, $target_host, $snapshot) {
        global $wpdb;
        $project_id = intval($project_id);
        $target_host = strtolower(trim((string) $target_host));
        if ($project_id <= 0 || $target_host === '' || !is_array($snapshot)) {
            return false;
        }

        $this->aysa_monitoring_ai_history_maybe_create_table();
        $table = esc_sql($this->aysa_monitoring_ai_history_table());
        $summary = isset($snapshot['summary']) && is_array($snapshot['summary']) ? $snapshot['summary'] : array();
        $query_pack = isset($snapshot['query_pack']) && is_array($snapshot['query_pack']) ? $snapshot['query_pack'] : array();
        $snapshot_id = intval($snapshot['snapshot_id'] ?? 0);
        $snapshot_at = trim((string) ($snapshot['snapshot_at'] ?? ''));
        if ($snapshot_at === '') {
            $snapshot_at = current_time('mysql');
        } else {
            $ts = strtotime($snapshot_at);
            $snapshot_at = $ts ? gmdate('Y-m-d H:i:s', $ts) : current_time('mysql');
        }

        $existing_id = 0;
        if ($snapshot_id > 0) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $existing_id = intval($wpdb->get_var($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT id FROM {$table} WHERE project_id = %d AND target_host = %s AND snapshot_id = %d LIMIT 1",
                $project_id,
                $target_host,
                $snapshot_id
            )));
        }

        $data = array(
            'project_id' => $project_id,
            'target_host' => $target_host,
            'snapshot_id' => $snapshot_id,
            'status' => sanitize_text_field((string) ($snapshot['status'] ?? '')),
            'brand_name' => sanitize_text_field((string) ($snapshot['brand'] ?? '')),
            'summary_json' => wp_json_encode($summary),
            'query_pack_json' => wp_json_encode($query_pack),
            'snapshot_json' => wp_json_encode($snapshot),
            'snapshot_at' => $snapshot_at,
            'created_at' => current_time('mysql'),
        );
        $formats = array('%d', '%s', '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s');

        if ($existing_id > 0) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->update($table, $data, array('id' => $existing_id), $formats, array('%d'));
            $this->aysa_monitoring_update_client_rag_context($project_id, $target_host);
            return $existing_id;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $wpdb->insert($table, $data, $formats);
        $insert_id = intval($wpdb->insert_id);
        $this->aysa_monitoring_update_client_rag_context($project_id, $target_host);
        return $insert_id;
    }

    private function aysa_monitoring_rag_context_option_key($project_id) {
        return 'aysa_monitoring_client_rag_context_' . intval($project_id);
    }

    private function aysa_monitoring_update_client_rag_context($project_id, $target_host) {
        $project_id = intval($project_id);
        $target_host = strtolower(trim((string) $target_host));
        if ($project_id <= 0 || $target_host === '') {
            return;
        }

        $rank_rows = $this->aysa_adjustments_get_monitoring_latest_rows($project_id, $target_host);
        $rank_summary = array();
        foreach (array_slice(is_array($rank_rows) ? $rank_rows : array(), 0, 25) as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keyword = trim((string) ($row['keyword'] ?? ''));
            if ($keyword === '') {
                continue;
            }
            $position = isset($row['position']) && is_numeric($row['position']) ? intval(round((float) $row['position'])) : null;
            $entry = array(
                'keyword' => $keyword,
                'position' => $position,
                'previous_position' => isset($row['previous_position']) && is_numeric($row['previous_position']) ? intval(round((float) $row['previous_position'])) : null,
                'position_delta' => isset($row['position_delta']) && is_numeric($row['position_delta']) ? floatval($row['position_delta']) : null,
                'target_url' => (string) ($row['link'] ?? ''),
                'last_checked_at' => (string) ($row['last_checked_at'] ?? ''),
                'intent' => (string) ($row['intent'] ?? ''),
            );
            $rank_summary[] = $entry;
        }

        $ai_overview = $this->aysa_monitoring_ai_get_local_overview($project_id, $target_host);
        $ai_snapshot = isset($ai_overview['snapshot']) && is_array($ai_overview['snapshot']) ? $ai_overview['snapshot'] : array();
        $ai_questions = isset($ai_snapshot['questions']) && is_array($ai_snapshot['questions']) ? $ai_snapshot['questions'] : array();
        $ai_summary = array();
        foreach (array_slice($ai_questions, 0, 12) as $question) {
            if (!is_array($question)) {
                continue;
            }
            $ai_summary[] = array(
                'question' => (string) ($question['text'] ?? $question['question'] ?? ''),
                'type' => (string) ($question['type'] ?? ''),
                'visibility_status' => (string) ($question['visibility_status'] ?? ''),
                'our_position' => isset($question['our_position']) ? $question['our_position'] : null,
                'context_summary' => (string) ($question['context_summary'] ?? ''),
                'snapshot_at' => (string) ($ai_snapshot['snapshot_at'] ?? ''),
            );
        }

        $payload = array(
            'project_id' => $project_id,
            'target_host' => $target_host,
            'updated_at' => current_time('mysql'),
            'rank_monitoring' => $rank_summary,
            'ai_monitoring' => $ai_summary,
        );

        $this->aysa_monitoring_sync_client_context_to_broker($payload);
        update_option($this->aysa_monitoring_rag_context_option_key($project_id), $payload, false);
    }

    private function aysa_monitoring_sync_client_context_to_broker($payload) {
        if (!is_array($payload)) {
            return;
        }

        $project_id = intval($payload['project_id'] ?? 0);
        $target_host = strtolower(trim((string) ($payload['target_host'] ?? '')));
        if ($project_id <= 0 || $target_host === '') {
            return;
        }

        $result = $this->aysa_broker_request('POST', '/v1/project-context/monitoring/upsert', $payload, 20);
        if (is_wp_error($result)) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Release-reviewed diagnostic only; the message contains no credentials or request payloads and runs only in opt-in debugging or the administrative uninstall lifecycle.
            error_log('AYSA monitoring context broker sync failed for project ' . $project_id . ': ' . $result->get_error_message());
        }
    }

    private function aysa_monitoring_ai_get_local_overview($project_id, $target_host) {
        $history = $this->aysa_monitoring_ai_get_local_history($project_id, $target_host, 12);
        if (empty($history)) {
            return array();
        }

        $latest = $history[0];
        $snapshot = isset($latest['_snapshot']) && is_array($latest['_snapshot']) ? $latest['_snapshot'] : array();
        $clean_history = array_map(function($row) {
            if (is_array($row) && isset($row['_snapshot'])) {
                unset($row['_snapshot']);
            }
            return $row;
        }, $history);

        return array(
            'ok' => true,
            'project_id' => intval($project_id),
            'domain' => (string) $target_host,
            'snapshot' => $snapshot,
            'history' => $clean_history,
        );
    }

    private function aysa_monitoring_ai_get_local_history($project_id, $target_host, $limit = 12) {
        global $wpdb;
        $project_id = intval($project_id);
        $target_host = strtolower(trim((string) $target_host));
        if ($project_id <= 0 || $target_host === '') {
            return array();
        }

        $table = esc_sql($this->aysa_monitoring_ai_history_table());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table));
        if ($exists !== $table) {
            return array();
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $rows = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT * FROM {$table} WHERE project_id = %d AND target_host = %s ORDER BY snapshot_at DESC LIMIT %d",
            $project_id,
            $target_host,
            max(1, min(30, intval($limit)))
        ), ARRAY_A);
        if (!is_array($rows)) {
            return array();
        }

        $history = array();
        foreach ($rows as $row) {
            $summary = json_decode((string) ($row['summary_json'] ?? '{}'), true);
            $query_pack = json_decode((string) ($row['query_pack_json'] ?? '[]'), true);
            $snapshot = json_decode((string) ($row['snapshot_json'] ?? '{}'), true);
            $history[] = array(
                'snapshot_id' => intval($row['snapshot_id'] ?? 0),
                'project_id' => intval($row['project_id'] ?? 0),
                'domain' => (string) ($row['target_host'] ?? ''),
                'brand' => (string) ($row['brand_name'] ?? ''),
                'status' => (string) ($row['status'] ?? ''),
                'snapshot_at' => gmdate('c', strtotime((string) ($row['snapshot_at'] ?? 'now'))),
                'query_pack' => is_array($query_pack) ? $query_pack : array(),
                'summary' => is_array($summary) ? $summary : array(),
                '_snapshot' => is_array($snapshot) ? $snapshot : array(),
            );
        }

        return $history;
    }

    private function aysa_monitoring_resolve_context($requested_target = '') {
        $configured_target = (string) get_option('aysa_domain', '');
        if ($configured_target === '') {
            $configured_target = (string) get_option('aysa_auth_domain', '');
        }
        if ($configured_target === '') {
            $configured_target = (string) home_url();
        }

        $effective_target = $requested_target !== '' ? $requested_target : $configured_target;
        $host = $this->aysa_monitoring_extract_host($effective_target);
        if ($host === '' || $this->aysa_monitoring_is_local_host($host)) {
            $host = $this->aysa_monitoring_extract_host($configured_target);
        }
        if ($host === '' || $this->aysa_monitoring_is_local_host($host)) {
            $host = $this->aysa_monitoring_extract_host((string) home_url());
        }

        $lang_raw = (string) get_option('aysa_language', get_locale());
        $language_code = strtolower(substr($lang_raw, 0, 2));
        if (!preg_match('/^[a-z]{2}$/', $language_code)) {
            $language_code = 'en';
        }

        $country_raw = (string) get_option('aysa_targeted_country', '');
        if ($country_raw === '') {
            $country_raw = (string) get_option('targeted_country', '');
        }
        if ($country_raw === '') {
            $country_raw = (string) get_option('aysa_country', '');
        }
        if ($country_raw === '') {
            $country_raw = (string) get_option('country', '');
        }

        $tld = strtolower((string) preg_replace('/^.*\.([a-z]{2,})$/', '$1', $host));
        if ($country_raw === '') {
            if ($tld === 'ro') {
                $country_raw = 'Romania';
            } elseif ($tld === 'de') {
                $country_raw = 'Germany';
            } elseif ($tld === 'fr') {
                $country_raw = 'France';
            } elseif ($tld === 'it') {
                $country_raw = 'Italy';
            } elseif ($tld === 'es') {
                $country_raw = 'Spain';
            } elseif ($tld === 'uk' || $tld === 'co.uk') {
                $country_raw = 'United Kingdom';
            } elseif ($tld === 'com') {
                $country_raw = 'United States';
            }
        }

        $location_map = array(
            'romania' => 2642,
            'ro' => 2642,
            'germany' => 2276,
            'de' => 2276,
            'france' => 2250,
            'fr' => 2250,
            'italy' => 2380,
            'it' => 2380,
            'spain' => 2724,
            'es' => 2724,
            'united kingdom' => 2826,
            'uk' => 2826,
            'united states' => 2840,
            'us' => 2840,
        );
        $country_key = strtolower(trim($country_raw));
        $location_code = isset($location_map[$country_key]) ? intval($location_map[$country_key]) : 2840;

	        if ($tld === 'ro') {
	            $se_domain = 'google.ro';
	            if ($language_code === 'en') {
	                $language_code = 'ro';
	            }
	            $location_code = 2642;
        } elseif ($tld === 'de') {
            $se_domain = 'google.de';
        } elseif ($tld === 'fr') {
            $se_domain = 'google.fr';
        } elseif ($tld === 'it') {
            $se_domain = 'google.it';
        } elseif ($tld === 'es') {
            $se_domain = 'google.es';
        } elseif ($tld === 'uk') {
            $se_domain = 'google.co.uk';
	        } else {
	            $se_domain = 'google.com';
	        }
        return array(
            'target' => $host,
            'se_domain' => $se_domain,
            'location_code' => $location_code,
            'language_code' => $language_code,
        );
    }

    private function aysa_monitoring_extract_host($value) {
        $value = trim((string) $value);
        if ($value === '') {
            return '';
        }
        if (!preg_match('#^[a-z][a-z0-9+.-]*://#i', $value)) {
            $value = 'https://' . ltrim($value, '/');
        }
        $host = wp_parse_url($value, PHP_URL_HOST);
        if (!is_string($host) || $host === '') {
            return '';
        }
        return strtolower(preg_replace('/^www\./', '', $host));
    }

    private function aysa_monitoring_is_local_host($host) {
        $host = strtolower(trim((string) $host));
        return $host === 'localhost' || $host === '127.0.0.1' || substr($host, -10) === '.localhost';
    }

private function aysa_monitoring_history_table() {
        global $wpdb;
        return $wpdb->prefix . 'aysa_monitoring_rank_history';
    }

    private function aysa_monitoring_ai_history_table() {
        global $wpdb;
        return $wpdb->prefix . 'aysa_monitoring_ai_history';
    }

    private function aysa_sitemap_competition_runs_table() {
        global $wpdb;
        return $wpdb->prefix . 'aysa_sitemap_competition_runs';
    }

    private function aysa_sitemap_competition_items_table() {
        global $wpdb;
        return $wpdb->prefix . 'aysa_sitemap_competition_items';
    }

    private function aysa_sitemap_competition_todo_table() {
        global $wpdb;
        return $wpdb->prefix . 'aysa_sitemap_competition_todo';
    }

    private function aysa_monitoring_history_create_table() {
        global $wpdb;
        $table = esc_sql($this->aysa_monitoring_history_table());
        $charset_collate = $wpdb->get_charset_collate();
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
	        $sql = "CREATE TABLE {$table} (
	            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
	            project_id BIGINT UNSIGNED NOT NULL,
	            target_host VARCHAR(191) NOT NULL,
	            keyword VARCHAR(191) NOT NULL,
	            associated_on VARCHAR(40) NOT NULL DEFAULT 'other',
	            id_intern BIGINT UNSIGNED NOT NULL DEFAULT 0,
	            target_url TEXT NULL,
	            position DECIMAL(10,4) NULL,
	            position_absolute DECIMAL(10,4) NULL,
	            ctr DECIMAL(10,4) NULL,
	            search_volume INT NULL,
	            bounce_rate DECIMAL(10,4) NULL,
	            sessions INT NULL,
	            gsc_clicks INT NULL,
            gsc_impressions INT NULL,
            intent VARCHAR(32) NULL,
            traffic INT NULL,
            traffic_pct DECIMAL(10,4) NULL,
            kd DECIMAL(10,4) NULL,
            item_types TEXT NULL,
	            check_url TEXT NULL,
	            matched_item_type VARCHAR(64) NULL,
	            source VARCHAR(20) NOT NULL DEFAULT 'api',
	            tracked_at DATETIME NOT NULL,
	            created_at DATETIME NOT NULL,
            PRIMARY KEY (id),
            KEY project_target (project_id, target_host),
            KEY keyword_idx (keyword),
            KEY tracked_at_idx (tracked_at),
            KEY kw_assoc_entity (project_id, keyword, associated_on, id_intern)
        ) {$charset_collate};";
        dbDelta($sql);
    }

    private function aysa_monitoring_ai_history_create_table() {
        global $wpdb;
        $table = esc_sql($this->aysa_monitoring_ai_history_table());
        $charset_collate = $wpdb->get_charset_collate();
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        $sql = "CREATE TABLE {$table} (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            project_id BIGINT UNSIGNED NOT NULL,
            target_host VARCHAR(191) NOT NULL,
            snapshot_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
            status VARCHAR(32) NOT NULL DEFAULT '',
            brand_name VARCHAR(191) NOT NULL DEFAULT '',
            summary_json LONGTEXT NOT NULL,
            query_pack_json LONGTEXT NOT NULL,
            snapshot_json LONGTEXT NOT NULL,
            snapshot_at DATETIME NOT NULL,
            created_at DATETIME NOT NULL,
            PRIMARY KEY (id),
            KEY project_target_snapshot (project_id, target_host, snapshot_at),
            KEY snapshot_id_idx (snapshot_id)
        ) {$charset_collate};";
        dbDelta($sql);
    }

    public function aysa_sitemap_competition_maybe_create_tables() {
        global $wpdb;
        $charset_collate = $wpdb->get_charset_collate();
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';

        $runs = esc_sql($this->aysa_sitemap_competition_runs_table());
        $items = esc_sql($this->aysa_sitemap_competition_items_table());
        $todo = esc_sql($this->aysa_sitemap_competition_todo_table());

        dbDelta("CREATE TABLE {$runs} (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            project_id BIGINT UNSIGNED NOT NULL,
            target_host VARCHAR(191) NOT NULL,
            remote_run_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
            status VARCHAR(32) NOT NULL DEFAULT '',
            competitors_json LONGTEXT NOT NULL,
            summary_json LONGTEXT NOT NULL,
            overview_json LONGTEXT NOT NULL,
            run_json LONGTEXT NOT NULL,
            started_at DATETIME NULL,
            completed_at DATETIME NULL,
            created_at DATETIME NOT NULL,
            updated_at DATETIME NOT NULL,
            PRIMARY KEY (id),
            KEY project_target_idx (project_id, target_host),
            KEY remote_run_idx (remote_run_id)
        ) {$charset_collate};");

        dbDelta("CREATE TABLE {$items} (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            local_run_id BIGINT UNSIGNED NOT NULL,
            project_id BIGINT UNSIGNED NOT NULL,
            target_host VARCHAR(191) NOT NULL,
            competitor_host VARCHAR(191) NOT NULL,
            final_url TEXT NOT NULL,
            page_title TEXT NULL,
            meta_title TEXT NULL,
            content_type VARCHAR(32) NOT NULL DEFAULT 'unknown',
            freshness_signal VARCHAR(191) NULL,
            slug VARCHAR(191) NULL,
            path TEXT NULL,
            fetch_status VARCHAR(32) NOT NULL DEFAULT '',
            item_json LONGTEXT NOT NULL,
            created_at DATETIME NOT NULL,
            PRIMARY KEY (id),
            KEY local_run_idx (local_run_id),
            KEY competitor_idx (competitor_host(120)),
            KEY content_type_idx (content_type)
        ) {$charset_collate};");

        dbDelta("CREATE TABLE {$todo} (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            local_run_id BIGINT UNSIGNED NOT NULL,
            project_id BIGINT UNSIGNED NOT NULL,
            target_host VARCHAR(191) NOT NULL,
            competitor_host VARCHAR(191) NOT NULL,
            competitor_url TEXT NOT NULL,
            competitor_title TEXT NULL,
            estimated_topic TEXT NULL,
            freshness_signal VARCHAR(191) NULL,
            local_status VARCHAR(32) NOT NULL DEFAULT 'missing',
            suggested_action VARCHAR(32) NOT NULL DEFAULT 'review',
            matched_post_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
            matched_url TEXT NULL,
            todo_json LONGTEXT NOT NULL,
            created_at DATETIME NOT NULL,
            PRIMARY KEY (id),
            KEY local_run_idx (local_run_id),
            KEY status_idx (local_status)
        ) {$charset_collate};");
    }

    private function aysa_monitoring_extract_numeric($row, $keys) {
        if (!is_array($row)) {
            return null;
        }
        foreach ($keys as $key) {
            if (!array_key_exists($key, $row)) {
                continue;
            }
            $val = $row[$key];
            if ($val === null || $val === '') {
                continue;
            }
            if (is_numeric($val)) {
                return floatval($val);
            }
            if (is_string($val)) {
                $normalized = str_replace(',', '.', preg_replace('/[^0-9,\.\-]/', '', $val));
                if ($normalized !== '' && is_numeric($normalized)) {
                    return floatval($normalized);
                }
            }
        }
        return null;
    }

    private function aysa_monitoring_persist_rows($project_id, $target_host, $rows) {
        global $wpdb;
        if (!is_array($rows) || empty($rows)) {
            return array('inserted' => 0, 'failed' => 0, 'last_error' => '');
        }
        $this->aysa_monitoring_history_maybe_create_table();
        $table = esc_sql($this->aysa_monitoring_history_table());
        $now = current_time('mysql');
        $inserted = 0;
        $failed = 0;
        $last_error = '';

        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keyword = trim((string) ($row['keyword'] ?? ''));
            if ($keyword === '') {
                continue;
            }
	            // Keep Position as organic group rank first, then generic fallbacks.
	            $position = $this->aysa_monitoring_extract_numeric($row, array(
	                'position', 'rank_group', 'last_position', 'rank', 'pos'
	            ));
	            $position_absolute = $this->aysa_monitoring_extract_numeric($row, array(
	                'position_absolute', 'rank_absolute'
	            ));
	            // Prefer Google CTR when present.
            $ctr = $this->aysa_monitoring_extract_numeric($row, array('gsc_ctr', 'ctr'));
            $intent = trim((string) ($row['intent'] ?? $row['search_intent'] ?? ''));
            $traffic = $this->aysa_monitoring_extract_numeric($row, array('traffic', 'gsc_clicks', 'clicks', 'sessions'));
            $traffic_pct = $this->aysa_monitoring_extract_numeric($row, array('traffic_pct', 'traffic_percent'));
            $kd = $this->aysa_monitoring_extract_numeric($row, array('kd', 'keyword_difficulty', 'competition'));
	            $search_volume = $this->aysa_monitoring_extract_numeric($row, array('search_volume', 'searchVolume', 'volume'));
	            // Prefer GA bounce rate when present.
	            $bounce_rate = $this->aysa_monitoring_extract_numeric($row, array('ga_bounce_rate', 'bounce_rate', 'bounceRate'));
	            $sessions = $this->aysa_monitoring_extract_numeric($row, array('sessions'));
	            $gsc_clicks = $this->aysa_monitoring_extract_numeric($row, array('gsc_clicks', 'clicks'));
	            $gsc_impressions = $this->aysa_monitoring_extract_numeric($row, array('gsc_impressions', 'impressions'));
            $itemTypesArray = [];
            if (isset($row['item_types']) && is_array($row['item_types'])) {
                $itemTypesArray = array_values(array_filter(array_map('strval', $row['item_types']), fn($v) => $v !== ''));
            } elseif (isset($row['item_types']) && is_string($row['item_types']) && trim($row['item_types']) !== '') {
                $itemTypesArray = array_values(array_filter(array_map('trim', explode(',', (string) $row['item_types'])), fn($v) => $v !== ''));
            }
            $itemTypesJson = null;
            if ($itemTypesArray) {
                $encoded = json_encode($itemTypesArray);
                if ($encoded !== false) {
                    $itemTypesJson = $encoded;
                }
            }
	            $check_url = (string) ($row['check_url'] ?? '');
	            $matched_item_type = (string) ($row['matched_item_type'] ?? '');
            $status_code = isset($row['status_code']) ? intval($row['status_code']) : null;

            // Do not persist queue/incomplete responses as latest history rows.
            if ($status_code === 40602) {
                continue;
            }

            $target_url = (string) ($row['link'] ?? $row['found_url'] ?? $row['url'] ?? '');
            $hasMeaningfulData = (
                (!is_null($position) && floatval($position) > 0) ||
                (!is_null($position_absolute) && floatval($position_absolute) > 0) ||
                (!is_null($ctr) && floatval($ctr) > 0) ||
                (!is_null($search_volume) && floatval($search_volume) > 0) ||
                (!is_null($bounce_rate) && floatval($bounce_rate) > 0) ||
                (!is_null($sessions) && floatval($sessions) > 0) ||
                (!is_null($gsc_clicks) && floatval($gsc_clicks) > 0) ||
                (!is_null($gsc_impressions) && floatval($gsc_impressions) > 0) ||
                ($intent !== '') ||
                (!is_null($traffic) && floatval($traffic) > 0) ||
                (!is_null($traffic_pct) && floatval($traffic_pct) > 0) ||
                (!is_null($kd) && floatval($kd) > 0) ||
                $target_url !== '' ||
                $itemTypesJson !== null ||
                $check_url !== '' ||
                $matched_item_type !== ''
            );
            if (!$hasMeaningfulData) {
                continue;
            }

            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $ok = $wpdb->insert(
                $table,
                array(
                    'project_id' => intval($project_id),
                    'target_host' => (string) $target_host,
                    'keyword' => $keyword,
                    'associated_on' => (string) ($row['associated_on'] ?? $row['associatedOn'] ?? 'other'),
	                    'id_intern' => intval($row['id_intern'] ?? $row['idIntern'] ?? 0),
	                    'target_url' => $target_url,
	                    'position' => is_null($position) ? null : $position,
	                    'position_absolute' => is_null($position_absolute) ? null : $position_absolute,
	                    'ctr' => is_null($ctr) ? null : $ctr,
	                    'search_volume' => is_null($search_volume) ? null : intval(round($search_volume)),
	                    'bounce_rate' => is_null($bounce_rate) ? null : $bounce_rate,
	                    'sessions' => is_null($sessions) ? null : intval(round($sessions)),
	                    'gsc_clicks' => is_null($gsc_clicks) ? null : intval(round($gsc_clicks)),
	                    'gsc_impressions' => is_null($gsc_impressions) ? null : intval(round($gsc_impressions)),
                    'intent' => $intent !== '' ? $intent : null,
                    'traffic' => is_null($traffic) ? null : intval(round($traffic)),
                    'traffic_pct' => is_null($traffic_pct) ? null : $traffic_pct,
                    'kd' => is_null($kd) ? null : $kd,
                    'item_types' => $itemTypesJson,
	                    'check_url' => $check_url !== '' ? $check_url : null,
	                    'matched_item_type' => $matched_item_type !== '' ? $matched_item_type : null,
	                    'source' => 'api',
	                    'tracked_at' => $now,
	                    'created_at' => $now,
	                ),
	                array(
	                    '%d', '%s', '%s', '%s', '%d', '%s',
	                    '%f', '%f', '%f', '%d', '%f', '%d', '%d', '%d',
                    '%s', '%d', '%f', '%f',
	                    '%s', '%s', '%s', '%s', '%s', '%s'
	                )
	            );
            if ($ok === false) {
                $failed++;
                if (!empty($wpdb->last_error)) {
                    $last_error = (string) $wpdb->last_error;
                }
            } else {
                $inserted++;
            }
        }
        if ($inserted > 0) {
            $this->aysa_monitoring_update_client_rag_context($project_id, $target_host);
        }

        return array(
            'inserted' => $inserted,
            'failed' => $failed,
            'last_error' => $last_error,
        );
    }

    private function aysa_get_project_credit_balance($project_id, $force_remote = false) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return 0;
        }

        if (class_exists('Aysa_Wallet_Manager')) {
            try {
                $wallet = Aysa_Wallet_Manager::get_instance()->get_wallet_snapshot($project_id, array(
                    'allow_remote' => true,
                    'force_remote' => !empty($force_remote),
                ));
                if (is_array($wallet) && isset($wallet['balance_cents'])) {
                    return intval($wallet['balance_cents']);
                }
            } catch (Exception $e) {
                // keep backward-compat fallback below
            }
        }

        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }

        try {
            $auth_manager = Aysa_Auth_Manager::get_instance();
            $resp = $auth_manager->make_authenticated_request('api/AysaCredits/get-total-credits/' . $project_id, 'GET');
            if (is_array($resp)) {
                if (isset($resp['totalCredits'])) {
                    return intval($resp['totalCredits']);
                }
                if (isset($resp['data']['totalCredits'])) {
                    return intval($resp['data']['totalCredits']);
                }
                if (isset($resp['credits'])) {
                    return intval($resp['credits']);
                }
            }
            if (is_numeric($resp)) {
                return intval($resp);
            }
        } catch (Exception $e) {
            // fallback below
        }

        return intval(get_option('aysa_user_credits', 0));
    }

    private function aysa_get_adverlink_orders_key($project_id) {
        return 'aysa_adverlink_orders_' . intval($project_id);
    }

    private function aysa_get_reserved_credits_key($project_id) {
        return 'aysa_adverlink_reserved_credits_' . intval($project_id);
    }

    private function aysa_get_adverlink_link_issues_key($project_id) {
        return 'aysa_adverlink_link_issues_' . intval($project_id);
    }

    private function aysa_get_adverlink_link_issues_sync_key($project_id) {
        return 'aysa_adverlink_link_issues_last_sync_' . intval($project_id);
    }

    private function aysa_get_backlink_audit_key($project_id) {
        return 'aysa_backlink_audit_' . intval($project_id);
    }

    private function aysa_get_backlink_audit_sync_key($project_id) {
        return 'aysa_backlink_audit_last_sync_' . intval($project_id);
    }

    private function aysa_get_backlink_strategy_key($project_id) {
        return 'aysa_backlink_strategy_' . intval($project_id);
    }

    private function aysa_get_backlink_strategy_sync_key($project_id) {
        return 'aysa_backlink_strategy_last_sync_' . intval($project_id);
    }

    private function aysa_get_backlink_audit_domain_key($project_id, $domain) {
        $normalized = strtolower(trim((string) $domain));
        $normalized = preg_replace('/[^a-z0-9.-]/', '', $normalized);
        if ($normalized === '') {
            $normalized = 'unknown';
        }
        return 'aysa_backlink_audit_domain_' . intval($project_id) . '_' . md5($normalized);
    }

    private function aysa_get_article_draft_key($project_id, $portal_id) {
        $project_part = intval($project_id);
        $portal_part = substr(md5((string) $portal_id), 0, 16);
        return 'aysa_adverlink_article_draft_' . $project_part . '_' . $portal_part;
    }

    private function aysa_get_adverlink_credentials() {
        $api_key = defined('AYSA_ADVERLINK_API_KEY')
            ? (string) AYSA_ADVERLINK_API_KEY
            : (string) get_option('aysa_adverlink_api_key', '');

        $org_id = defined('AYSA_ADVERLINK_ADVERTISER_ORG_ID')
            ? (string) AYSA_ADVERLINK_ADVERTISER_ORG_ID
            : (string) get_option('aysa_adverlink_advertiser_org_id', '');

        return array(
            'api_key' => trim($api_key),
            'org_id' => trim($org_id)
        );
    }

    private function aysa_detect_adverlink_credential_type($api_key) {
        $api_key = trim((string) $api_key);
        if ($api_key === '') {
            return 'missing';
        }
        if (stripos($api_key, 'atk_') === 0) {
            return 'advertiser_api_key';
        }
        if (stripos($api_key, 'advlk_pk_') === 0 || stripos($api_key, 'advlk_sk_') === 0) {
            return 'plugin_token';
        }
        return 'unknown';
    }

    private function aysa_validate_adverlink_marketplace_credentials(array $creds) {
        $api_key = trim((string) ($creds['api_key'] ?? ''));
        if ($api_key === '') {
            return new WP_Error(
                'adverlink_missing_credentials',
                'Off-Page credentials are missing. Configure marketplace advertiser API key in AYSA settings.'
            );
        }

        $type = $this->aysa_detect_adverlink_credential_type($api_key);
        if ($type !== 'advertiser_api_key') {
            return new WP_Error(
                'adverlink_wrong_credential_type',
                'Received ' . $type . '. Off-Page marketplace public routes require advertiser API key (atk_...).'
            );
        }

        return true;
    }

    private function aysa_format_adverlink_marketplace_error($error, $fallback = 'Marketplace request failed.') {
        if (is_wp_error($error)) {
            return array(
                'code' => $error->get_error_code(),
                'message' => $error->get_error_message(),
            );
        }

        if (is_array($error)) {
            return array(
                'code' => sanitize_text_field((string) ($error['code'] ?? 'marketplace_error')),
                'message' => sanitize_text_field((string) ($error['message'] ?? $fallback)),
            );
        }

        return array(
            'code' => 'marketplace_error',
            'message' => $fallback,
        );
    }

    private function aysa_get_adverlink_orders($project_id) {
        $orders = get_option($this->aysa_get_adverlink_orders_key($project_id), array());
        return is_array($orders) ? $orders : array();
    }

    private function aysa_get_link_exchange_profile($project_id = 0) {
        $project_id = intval($project_id);
        $setup_user_id = intval($this->aysa_get_authenticated_external_user_id());
        if ($setup_user_id <= 0) {
            $setup_user_id = get_current_user_id();
        }

        $context = $this->aysa_keyword_strategy_get_context($setup_user_id);
        $seed_data = $this->aysa_keyword_strategy_extract_seed_data($setup_user_id);
        $general = get_option('aysa_wizard_general_' . intval($setup_user_id), array());
        if (!is_array($general)) {
            $general = array();
        }

        $industry = sanitize_text_field((string) ($context['industry'] ?? $general['industry'] ?? ''));
        $website_type = sanitize_text_field((string) ($context['website_type'] ?? $general['website_type'] ?? ''));
        $country = sanitize_text_field((string) ($context['targeted_country'] ?? 'Romania'));
        $language = sanitize_text_field((string) ($context['primary_language'] ?? 'Romanian'));

        $topics = array();
        if (!empty($seed_data['seed_keywords']) && is_array($seed_data['seed_keywords'])) {
            foreach ($seed_data['seed_keywords'] as $keyword) {
                $keyword = sanitize_text_field((string) $keyword);
                if ($keyword !== '') {
                    $topics[] = $keyword;
                }
            }
        }
        $industry_hints = $this->aysa_adjustments_get_industry_keyword_hints($industry, $website_type);
        foreach ($industry_hints as $hint) {
            $hint = sanitize_text_field((string) $hint);
            if ($hint !== '') {
                $topics[] = $hint;
            }
        }
        $topics = array_values(array_unique(array_filter($topics, static function ($value) {
            return trim((string) $value) !== '';
        })));

        $authority_value = 0;
        if ($project_id > 0) {
            $authority_value = intval(get_option('aysa_domain_authority_score_' . $project_id, 0));
        }

        return array(
            'project_id' => $project_id,
            'site_url' => esc_url_raw(home_url('/')),
            'site_domain' => preg_replace('/^www\./', '', wp_parse_url(home_url('/'), PHP_URL_HOST) ?: ''),
            'website_type' => $website_type,
            'industry' => $industry,
            'language' => $language,
            'country' => $country,
            'topics' => $topics,
            'authority_value' => max(0, $authority_value),
            'auto_activation_enabled' => $this->aysa_link_exchange_auto_activation_enabled($project_id),
            'auto_activation_review_hours' => $this->aysa_exchange_auto_activation_review_hours(),
        );
    }

    private function aysa_link_exchange_auto_activation_enabled($project_id = 0) {
        $project_id = intval($project_id);
        $default = get_option('aysa_link_exchange_auto_activation_enabled', true);
        $value = $project_id > 0
            ? get_option('aysa_link_exchange_auto_activation_enabled_' . $project_id, $default)
            : $default;
        if (is_string($value)) {
            $value = !in_array(strtolower(trim($value)), array('0', 'false', 'no', 'off', 'disabled'), true);
        }
        return (bool) apply_filters('aysa_link_exchange_auto_activation_enabled', (bool) $value, $project_id);
    }

    private function aysa_exchange_find_auto_source_post_id($target_url, $anchor_text) {
        $query = new WP_Query(array(
            'post_type' => array('page', 'post', 'product'),
            'post_status' => 'publish',
            'posts_per_page' => 100,
            'orderby' => 'modified',
            'order' => 'DESC',
            'no_found_rows' => true,
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Reviewed WordPress query: the metadata selector is required by the feature and is constrained by the surrounding post type, status, limit, or administrative scope.
            'meta_query' => array(
                array(
                    'key' => '_elementor_data',
                    'compare' => 'NOT EXISTS',
                ),
            ),
        ));
        $best_id = 0;
        $best_score = -1;
        foreach ((array) $query->posts as $post) {
            if (!($post instanceof WP_Post)) {
                continue;
            }
            $slug = strtolower((string) $post->post_name);
            if (preg_match('/(checkout|cart|cos|cont|account|login|privacy|termeni|contact)/i', $slug)) {
                continue;
            }
            $semantic = $this->aysa_exchange_score_source_candidate($post, $target_url, $anchor_text);
            $score = intval($semantic['score'] ?? 0);
            if ((string) $post->post_type === 'page') {
                $score += 5;
            } elseif ((string) $post->post_type === 'product') {
                $score += 3;
            }
            if ($score > $best_score) {
                $best_score = $score;
                $best_id = intval($post->ID);
            }
        }
        return $best_id;
    }

    private function aysa_exchange_insert_link_into_post($post_id, $target_url, $anchor_text, $exchange_ref = '', $partner_box_html = '') {
        $post = get_post($post_id);
        if (!($post instanceof WP_Post)) {
            return new WP_Error('exchange_source_missing', 'Selected source content was not found.');
        }
        if ($post->post_status !== 'publish') {
            return new WP_Error('exchange_source_not_publish', 'Selected source content must be published before activation.');
        }

        $content = (string) $post->post_content;
        $target_url = esc_url_raw((string) $target_url);
        $anchor_text = sanitize_text_field((string) $anchor_text);
        if ($target_url === '' || $anchor_text === '') {
            return new WP_Error('exchange_target_invalid', 'Target URL and anchor text are required.');
        }

        $normalized_content = strtolower(html_entity_decode($content, ENT_QUOTES | ENT_HTML5, 'UTF-8'));
        $normalized_target = strtolower(html_entity_decode($target_url, ENT_QUOTES | ENT_HTML5, 'UTF-8'));
        $already_present = strpos($normalized_content, $normalized_target) !== false;

        $source_url = get_permalink($post);
        if (!$already_present) {
            $partner_box = trim((string) $partner_box_html);
            if ($partner_box === '') {
                $partner_box = $this->aysa_exchange_partner_box_html($target_url, $anchor_text, $exchange_ref);
            }
            $updated_content = rtrim($content) . "\n\n" . $partner_box;
            if (!is_string($updated_content) || trim($updated_content) === '') {
                return new WP_Error('exchange_insert_failed', 'Could not prepare the reciprocal link insertion.');
            }

            $update_result = wp_update_post(array(
                'ID' => intval($post->ID),
                'post_content' => $updated_content,
            ), true);
            if (is_wp_error($update_result)) {
                return new WP_Error('exchange_post_update_failed', $update_result->get_error_message());
            }
        }

        return array(
            'source_post_id' => intval($post->ID),
            'source_url' => esc_url_raw((string) $source_url),
            'already_present' => $already_present,
        );
    }

    private function aysa_exchange_remove_link_from_post($post_id, $exchange_ref = '') {
        $post = get_post($post_id);
        if (!($post instanceof WP_Post)) {
            return new WP_Error('exchange_source_missing', 'Selected source content was not found.');
        }

        $content = (string) $post->post_content;
        if ($content === '' || $exchange_ref === '') {
            return array(
                'source_post_id' => intval($post->ID),
                'removed' => false,
            );
        }

        $pattern = '/\s*<div class="aysa-exchange-partners-box"[^>]*data-aysa-exchange-ref="' . preg_quote($exchange_ref, '/') . '"[^>]*>.*?<\/div>\s*/is';
        $updated_content = preg_replace($pattern, "\n\n", $content, -1, $count);
        if (!is_string($updated_content)) {
            return new WP_Error('exchange_remove_failed', 'Could not prepare reciprocal link removal.');
        }
        if ((int) $count <= 0) {
            return array(
                'source_post_id' => intval($post->ID),
                'removed' => false,
            );
        }

        $update_result = wp_update_post(array(
            'ID' => intval($post->ID),
            'post_content' => trim($updated_content),
        ), true);
        if (is_wp_error($update_result)) {
            return new WP_Error('exchange_post_update_failed', $update_result->get_error_message());
        }

        return array(
            'source_post_id' => intval($post->ID),
            'removed' => true,
        );
    }

    private function aysa_exchange_truthy($value) {
        if ($value === true || $value === 1) {
            return true;
        }
        $normalized = strtolower(trim((string) $value));
        return in_array($normalized, array('1', 'true', 'yes', 'y', 'on', 'confirmed', 'confirm'), true);
    }

    private function aysa_exchange_rendered_post_content($post) {
        if (!($post instanceof WP_Post)) {
            return '';
        }

        if (class_exists('WP_REST_Request') && function_exists('rest_do_request') && function_exists('rest_get_server')) {
            $post_type_object = get_post_type_object((string) $post->post_type);
            $rest_base = $post_type_object && !empty($post_type_object->rest_base)
                ? (string) $post_type_object->rest_base
                : (string) $post->post_type;
            if ($rest_base !== '') {
                $rest_request = new WP_REST_Request('GET', '/wp/v2/' . trim($rest_base, '/') . '/' . intval($post->ID));
                $rest_response = rest_do_request($rest_request);
                if (!is_wp_error($rest_response)) {
                    $rest_data = rest_get_server()->response_to_data($rest_response, false);
                    if (is_array($rest_data) && isset($rest_data['content']) && is_array($rest_data['content'])) {
                        $rendered = (string) ($rest_data['content']['rendered'] ?? '');
                        if ($rendered !== '') {
                            return $rendered;
                        }
                    }
                }
            }
        }

        // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Intentional interoperability with the WordPress core content filter.
        return apply_filters('the_content', (string) $post->post_content);
    }

    private function aysa_exchange_partner_box_html($target_url, $anchor_text, $exchange_ref) {
        $link_html = sprintf(
            '<a href="%s" class="aysa-exchange-link" rel="noopener" target="_blank" title="%s" data-aysa-exchange-ref="%s">%s</a>',
            esc_url($target_url),
            esc_attr($anchor_text),
            esc_attr((string) $exchange_ref),
            esc_html($anchor_text)
        );

        return sprintf(
            '<div class="aysa-exchange-partners-box" data-aysa-exchange-ref="%s" style="margin:24px 0 0;padding:16px 18px;border:1px solid #d7dce2;border-radius:12px;background:#f8fafc;"><strong>Parteneri:</strong> %s</div>',
            esc_attr((string) $exchange_ref),
            $link_html
        );
    }

    private function aysa_exchange_event_resource_box_html($target_url, $anchor_text, $exchange_ref) {
        return sprintf(
            '<div class="aysa-exchange-partners-box" data-aysa-exchange-ref="%s"><strong>Resursă utilă pentru evenimente:</strong> Pentru decor floral la botez, vezi <a href="%s" rel="noopener" data-aysa-exchange-ref="%s">%s</a>.</div>',
            esc_attr((string) $exchange_ref),
            esc_url($target_url),
            esc_attr((string) $exchange_ref),
            esc_html($anchor_text)
        );
    }

    private function aysa_exchange_validate_confirmed_partner_box_html($partner_box_html, $target_url, $anchor_text, $exchange_ref) {
        $partner_box_html = trim((string) $partner_box_html);
        if ($partner_box_html === '') {
            return '';
        }
        if (stripos($partner_box_html, '<script') !== false || preg_match('/\son[a-z]+\s*=/i', $partner_box_html)) {
            return new WP_Error('exchange_partner_box_html_unsafe', 'Confirmed partner box contains unsafe markup.');
        }
        if (substr_count($partner_box_html, 'aysa-exchange-partners-box') !== 1 || substr_count($partner_box_html, 'data-aysa-exchange-ref="' . (string) $exchange_ref . '"') < 2) {
            return new WP_Error('exchange_partner_box_html_ref_mismatch', 'Confirmed partner box must contain the exact exchange reference.');
        }
        if (substr_count($partner_box_html, 'href="' . (string) $target_url . '"') !== 1) {
            return new WP_Error('exchange_partner_box_html_target_mismatch', 'Confirmed partner box must contain the exact target URL once.');
        }
        if (strpos($partner_box_html, 'rel="noopener"') === false) {
            return new WP_Error('exchange_partner_box_html_rel_missing', 'Confirmed partner box must use rel=noopener.');
        }
        $plain_text = trim(wp_strip_all_tags($partner_box_html));
        if (strpos($plain_text, (string) $anchor_text) === false) {
            return new WP_Error('exchange_partner_box_html_anchor_mismatch', 'Confirmed partner box must contain the exact anchor text.');
        }
        return $partner_box_html;
    }

    private function aysa_exchange_create_placement_backup_file($project_id, $post, array $context) {
        if (!($post instanceof WP_Post)) {
            return new WP_Error('exchange_backup_source_missing', 'Source content was not found for backup.');
        }
        $uploads = wp_upload_dir(null, false);
        if (!empty($uploads['error']) || empty($uploads['basedir'])) {
            return new WP_Error('exchange_backup_path_missing', 'WordPress uploads directory is unavailable.');
        }

        $backup_dir = trailingslashit((string) $uploads['basedir']) . 'aysa-private-backups/link-exchange';
        if (!wp_mkdir_p($backup_dir)) {
            return new WP_Error('exchange_backup_dir_failed', 'Could not prepare backup directory.');
        }

        $deny_file = trailingslashit($backup_dir) . '.htaccess';
        if (!file_exists($deny_file)) {
            @file_put_contents($deny_file, "Deny from all\n");
        }

        $exchange_ref = sanitize_file_name((string) ($context['exchange_ref'] ?? 'exchange'));
        $file = trailingslashit($backup_dir) . gmdate('Ymd-His') . '-' . intval($post->ID) . '-' . $exchange_ref . '.json';
        $backup_payload = array(
            'created_at' => gmdate('c'),
            'project_id' => intval($project_id),
            'source_post_id' => intval($post->ID),
            'post_type' => (string) $post->post_type,
            'post_status' => (string) $post->post_status,
            'post_name' => (string) $post->post_name,
            'post_content_base64' => base64_encode((string) $post->post_content),
            'post_content_hash' => hash('sha256', (string) $post->post_content),
            'rendered_content_hash' => hash('sha256', $this->aysa_exchange_rendered_post_content($post)),
            'context' => $context,
        );

        $written = @file_put_contents($file, wp_json_encode($backup_payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), LOCK_EX);
        if (!is_int($written) || $written <= 0) {
            return new WP_Error('exchange_backup_write_failed', 'Could not write placement backup.');
        }

        return array(
            'backup_file' => $file,
            'backup_hash' => hash_file('sha256', $file),
            'restore_to_hash' => (string) $backup_payload['post_content_hash'],
            'rendered_before_hash' => (string) $backup_payload['rendered_content_hash'],
        );
    }

    private function aysa_exchange_public_placement_readback($source_url, $target_url, $anchor_text, $exchange_ref) {
        $response = wp_remote_get(esc_url_raw((string) $source_url), array(
            'timeout' => 20,
            'redirection' => 3,
        ));
        if (is_wp_error($response)) {
            return array(
                'verified' => false,
                'http_status' => 0,
                'error' => $response->get_error_message(),
            );
        }

        $status = intval(wp_remote_retrieve_response_code($response));
        $body = (string) wp_remote_retrieve_body($response);
        $box_pattern = '/<div\b(?=[^>]*class=["\'][^"\']*aysa-exchange-partners-box[^"\']*["\'])(?=[^>]*data-aysa-exchange-ref=["\']' . preg_quote((string) $exchange_ref, '/') . '["\'])[^>]*>/i';
        $box_count = preg_match_all($box_pattern, $body);
        $link_count = 0;
        if (preg_match_all('/<a\b[^>]*>.*?<\/a>/is', $body, $anchors)) {
            foreach ((array) ($anchors[0] ?? array()) as $anchor_html) {
                $href = '';
                $ref = '';
                if (preg_match('/\bhref=["\']([^"\']+)["\']/i', (string) $anchor_html, $m)) {
                    $href = html_entity_decode((string) $m[1], ENT_QUOTES | ENT_HTML5, 'UTF-8');
                }
                if (preg_match('/\bdata-aysa-exchange-ref=["\']([^"\']+)["\']/i', (string) $anchor_html, $m)) {
                    $ref = html_entity_decode((string) $m[1], ENT_QUOTES | ENT_HTML5, 'UTF-8');
                }
                $label = trim(wp_strip_all_tags((string) $anchor_html));
                if (
                    $this->aysa_exchange_urls_match($href, (string) $target_url)
                    && hash_equals((string) $exchange_ref, $ref)
                    && $this->aysa_exchange_normalize_human_string($label) === $this->aysa_exchange_normalize_human_string((string) $anchor_text)
                ) {
                    $link_count++;
                }
            }
        }
        $has_noindex = preg_match('/<meta[^>]+name=["\']robots["\'][^>]+content=["\'][^"\']*noindex/i', $body) === 1;

        return array(
            'verified' => $status >= 200 && $status < 300 && intval($box_count) === 1 && intval($link_count) === 1 && !$has_noindex,
            'http_status' => $status,
            'partner_box_count' => intval($box_count),
            'partner_link_count' => intval($link_count),
            'noindex' => $has_noindex,
        );
    }

    private function aysa_exchange_apply_confirmed_partner_box($project_id, array $requester, array $request, array $input) {
        $expected_source_post_id = intval($input['source_post_id'] ?? 0);
        $expected_source_page = esc_url_raw((string) ($input['source_page'] ?? $input['source_url'] ?? ''));
        $expected_partner_url = esc_url_raw((string) ($input['partner_url'] ?? $input['target_url'] ?? ''));
        $expected_anchor = sanitize_text_field((string) ($input['anchor'] ?? $input['anchor_text'] ?? ''));
        $expected_before_hash = sanitize_text_field((string) ($input['before_hash'] ?? ''));
        $expected_block_hash = sanitize_text_field((string) ($input['inserted_block_hash'] ?? $input['block_hash'] ?? ''));
        $expected_partner_box_html = (string) ($input['partner_box_html'] ?? $input['confirmed_partner_box_html'] ?? '');
        $max_total_cost = intval($input['max_total_cost'] ?? $input['max_total_cost_credits'] ?? $input['max_cost'] ?? 0);

        if (!$this->aysa_exchange_truthy($input['confirmed'] ?? false) || !$this->aysa_exchange_truthy($input['backup_required'] ?? false) || !$this->aysa_exchange_truthy($input['rollback_required'] ?? false) || !$this->aysa_exchange_truthy($input['no_verification_refresh'] ?? false)) {
            return new WP_Error('exchange_controlled_confirmation_required', 'Controlled placement requires explicit confirmation, backup, rollback and no-verification flags.');
        }
        if ($max_total_cost <= 0 || $max_total_cost > 12) {
            return new WP_Error('exchange_cost_cap_invalid', 'Controlled placement requires a max cost cap of 12 credits or less.');
        }
        if ($expected_source_post_id <= 0 || $expected_source_page === '' || $expected_partner_url === '' || $expected_anchor === '' || $expected_before_hash === '' || $expected_block_hash === '') {
            return new WP_Error('exchange_controlled_payload_incomplete', 'Controlled placement payload is incomplete.');
        }

        $request_id = intval($request['id'] ?? 0);
        $exchange_ref = sanitize_text_field((string) ($request['exchange_ref'] ?? ''));
        if ($request_id !== intval($input['request_id'] ?? 0) || $exchange_ref !== sanitize_text_field((string) ($input['exchange_ref'] ?? ''))) {
            return new WP_Error('exchange_request_context_mismatch', 'Exchange request context does not match the confirmed payload.');
        }

        $role = (string) ($request['viewer_role'] ?? '');
        $status = (string) ($request['status'] ?? '');
        $obligation = (string) ($request['current_obligation'] ?? '');
        $is_requester_activation = $role === 'requester' && $status === 'accepted' && $obligation === 'requester_activation_required';
        $is_counterparty_activation = $role === 'counterparty' && $status === 'requester_published' && $obligation === 'counterparty_activation_required';
        $target_url = $role === 'requester'
            ? esc_url_raw((string) ($request['counterparty_target_url'] ?? ''))
            : esc_url_raw((string) ($request['requester_target_url'] ?? ''));
        $anchor_text = $role === 'requester'
            ? sanitize_text_field((string) ($request['counterparty_anchor_text'] ?? ''))
            : sanitize_text_field((string) ($request['requester_anchor_text'] ?? ''));
        if (!$this->aysa_exchange_urls_match($target_url, $expected_partner_url) || $this->aysa_exchange_normalize_human_string($anchor_text) !== $this->aysa_exchange_normalize_human_string($expected_anchor)) {
            return new WP_Error('exchange_target_context_mismatch', 'Partner URL or anchor does not match the exchange request.');
        }

        $source_post = get_post($expected_source_post_id);
        if (!($source_post instanceof WP_Post)) {
            return new WP_Error('exchange_source_missing', 'Selected source content was not found.');
        }
        $source_url = esc_url_raw((string) get_permalink($source_post));
        if ($source_url !== $expected_source_page) {
            return new WP_Error('exchange_source_url_mismatch', 'Source post URL does not match the confirmed source page.');
        }

        $rendered_content = $this->aysa_exchange_rendered_post_content($source_post);
        $current_hash = hash('sha256', $rendered_content);
        $partner_box = $this->aysa_exchange_validate_confirmed_partner_box_html($expected_partner_box_html, $expected_partner_url, $expected_anchor, $exchange_ref);
        if (is_wp_error($partner_box)) {
            return $partner_box;
        }
        if ($partner_box === '') {
            $partner_box = $this->aysa_exchange_partner_box_html($expected_partner_url, $expected_anchor, $exchange_ref);
        }
        $actual_block_hash = hash('sha256', $partner_box);
        if (!hash_equals($expected_block_hash, $actual_block_hash)) {
            return new WP_Error('exchange_partner_box_hash_mismatch', 'Confirmed partner box hash does not match the current insertion template.');
        }

        $readback_before = $this->aysa_exchange_public_placement_readback($expected_source_page, $expected_partner_url, $expected_anchor, $exchange_ref);
        $already_present = !empty($readback_before['verified']);
        if (!$already_present && !$is_requester_activation && !$is_counterparty_activation) {
            return new WP_Error('exchange_activation_state_not_supported', 'Exchange request is not in a supported controlled placement state.');
        }
        if (!$already_present && !hash_equals($expected_before_hash, $current_hash)) {
            return new WP_Error('exchange_before_hash_mismatch', 'Source content changed after preview. Placement was not applied.');
        }

        $backup = $this->aysa_exchange_create_placement_backup_file($project_id, $source_post, array(
            'request_id' => $request_id,
            'exchange_ref' => $exchange_ref,
            'source_page' => $expected_source_page,
            'partner_url' => $expected_partner_url,
            'anchor' => $expected_anchor,
            'before_hash' => $expected_before_hash,
            'inserted_block_hash' => $expected_block_hash,
        ));
        if (is_wp_error($backup)) {
            return $backup;
        }

        $activation = array(
            'source_post_id' => intval($source_post->ID),
            'source_url' => $source_url,
            'already_present' => true,
        );
        if (!$already_present) {
            $activation = $this->aysa_exchange_insert_link_into_post($expected_source_post_id, $expected_partner_url, $expected_anchor, $exchange_ref, $partner_box);
            if (is_wp_error($activation)) {
                return $activation;
            }
        }

        $readback_after = $this->aysa_exchange_public_placement_readback($expected_source_page, $expected_partner_url, $expected_anchor, $exchange_ref);
        if (empty($readback_after['verified'])) {
            return new WP_Error('exchange_public_readback_failed', 'Public readback did not verify the inserted partner box.');
        }

        $broker_result = array('request' => $request);
        $activated_now = false;
        if ($is_requester_activation || $is_counterparty_activation) {
            $payload = array(
                'project_id' => intval($project_id),
                'current_site_domain' => $requester['site_domain'],
                'request_id' => $request_id,
                'exchange_ref' => $exchange_ref,
                'source_post_id' => $expected_source_post_id,
                'source_url' => esc_url_raw((string) ($activation['source_url'] ?? $expected_source_page)),
                'published_url' => esc_url_raw((string) ($activation['source_url'] ?? $expected_source_page)),
                'target_url' => $expected_partner_url,
                'anchor_text' => $expected_anchor,
                'notes' => sanitize_textarea_field((string) ($input['notes'] ?? ($is_counterparty_activation ? 'OFF40 controlled counterparty placement apply' : 'OFF37 controlled placement apply'))),
            );
            $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/activate', $payload, 45);
            if (is_wp_error($broker_result)) {
                return new WP_Error('broker_error', $this->aysa_humanize_broker_error($broker_result, 'Could not activate reciprocal link.'));
            }
            $activated_now = true;
        }

        $billing_reference = 'offpage_exchange_activate_link_' . md5($exchange_ref . '|' . (string) $expected_source_post_id);
        $billing_status = $this->aysa_wallet_usage_reference_exists($project_id, $billing_reference) ? 'already_recorded' : 'not_recorded_no_activation';
        if ($activated_now && $billing_status !== 'already_recorded') {
            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_adverlink_exchange_activate_link',
                $billing_reference,
                array(
                    'module' => 'offpage',
                    'exchange_ref' => $exchange_ref,
                    'source_post_id' => $expected_source_post_id,
                    'readback_verified' => true,
                ),
                12,
                'wp_ajax/aysa_adverlink_exchange_activate_link'
            );
            $billing_status = 'recorded_after_readback';
        }

        return array(
            'request' => isset($broker_result['request']) && is_array($broker_result['request']) ? $broker_result['request'] : $request,
            'activation' => $activation,
            'activated_now' => $activated_now,
            'idempotent_replay' => $already_present && !$activated_now,
            'backup' => $backup,
            'before_hash' => $current_hash,
            'inserted_block_hash' => $actual_block_hash,
            'public_readback' => $readback_after,
            'billing' => array(
                'status' => $billing_status,
                'reference' => $billing_reference,
                'credits' => 12,
            ),
            'verification_refresh_ran' => false,
            'placement_mode' => 'controlled_partner_box',
        );
    }

    private function aysa_exchange_activation_grace_hours() {
        $hours = intval(apply_filters('aysa_exchange_activation_grace_hours', 48));
        return max(1, $hours);
    }

    private function aysa_exchange_activation_reminder_hours() {
        $hours = intval(apply_filters('aysa_exchange_activation_reminder_hours', 24));
        return max(1, $hours);
    }

    private function aysa_exchange_auto_activation_review_hours() {
        $hours = intval(apply_filters('aysa_exchange_auto_activation_review_hours', 24));
        return max(1, $hours);
    }

    private function aysa_exchange_request_review_started_ts(array $request) {
        foreach (array('accepted_at', 'created_at') as $key) {
            $value = trim((string) ($request[$key] ?? ''));
            if ($value === '') {
                continue;
            }
            $ts = strtotime($value);
            if ($ts > 0) {
                return $ts;
            }
        }
        return 0;
    }

    private function aysa_get_exchange_notification_state($project_id) {
        $state = get_option('aysa_exchange_notification_state_' . intval($project_id), array());
        return is_array($state) ? $state : array();
    }

    private function aysa_exchange_notification_sent($project_id, $key) {
        $state = $this->aysa_get_exchange_notification_state($project_id);
        return !empty($state[(string) $key]);
    }

    private function aysa_mark_exchange_notification_sent($project_id, $key) {
        $state = $this->aysa_get_exchange_notification_state($project_id);
        $state[(string) $key] = gmdate('c');
        update_option('aysa_exchange_notification_state_' . intval($project_id), $state, false);
    }

    private function aysa_exchange_send_activation_email($project_id, array $request, $reminder_type = 'activation') {
        $recipient = $this->get_notification_target_email();
        if ($recipient === '' || !is_email($recipient)) {
            return false;
        }

        $viewer_role = (string) ($request['viewer_role'] ?? '');
        $counterparty_domain = $viewer_role === 'requester'
            ? (string) ($request['counterparty_site_domain'] ?? '')
            : (string) ($request['requester_site_domain'] ?? '');
        $target_url = $viewer_role === 'requester'
            ? (string) ($request['requester_target_url'] ?? '')
            : (string) ($request['counterparty_target_url'] ?? '');
        $anchor_text = $viewer_role === 'requester'
            ? (string) ($request['requester_anchor_text'] ?? '')
            : (string) ($request['counterparty_anchor_text'] ?? '');
        $exchange_ref = (string) ($request['exchange_ref'] ?? '');
        $offpage_url = admin_url('admin.php?page=aysa-off-page-optimization');
        $is_ro = strpos(strtolower((string) aysa_get_interface_language()), 'ro') === 0;

        if ($is_ro) {
            $subject = $reminder_type === 'reminder_24h'
                ? 'Reminder Link Exchange: activeaza linkul reciproc'
                : 'Link Exchange: cealalta parte a activat, activeaza si tu';
            $body_lines = array(
                'Exchange ref: ' . $exchange_ref,
                'Partener: ' . $counterparty_domain,
                'Ancora ceruta: ' . $anchor_text,
                'URL tinta: ' . $target_url,
                '',
                $reminder_type === 'reminder_24h'
                    ? 'Reminder: cealalta parte a activat deja. Activeaza si tu linkul reciproc din plugin.'
                    : 'Cealalta parte a activat deja linkul. Intra in plugin si activeaza si tu linkul reciproc.',
                'Deschide pluginul: ' . $offpage_url,
            );
        } else {
            $subject = $reminder_type === 'reminder_24h'
                ? 'Link Exchange reminder: activate your reciprocal link'
                : 'Link Exchange: the counterparty activated, now activate yours';
            $body_lines = array(
                'Exchange ref: ' . $exchange_ref,
                'Counterparty: ' . $counterparty_domain,
                'Required anchor: ' . $anchor_text,
                'Target URL: ' . $target_url,
                '',
                $reminder_type === 'reminder_24h'
                    ? 'Reminder: the counterparty already activated their side. Please activate your reciprocal link in the plugin.'
                    : 'The counterparty has already activated their side. Please activate your reciprocal link in the plugin.',
                'Open plugin: ' . $offpage_url,
            );
        }

        $mail_result = $this->aysa_agent8_send_communication_email(
            $recipient,
            $subject,
            implode("\n", $body_lines),
            array(
                'module' => 'offpage_link_exchange',
                'trigger' => $reminder_type === 'reminder_24h' ? 'link_exchange_reminder' : 'link_exchange_activation_required',
                'project_id' => intval($project_id),
                'action_ref' => $exchange_ref !== '' ? 'link_exchange:' . $exchange_ref : 'link_exchange_project:' . intval($project_id),
                'deep_link' => $offpage_url,
                'reply_approval_supported' => false,
            )
        );

        return !empty($mail_result['ok']);
    }

    private function aysa_exchange_find_latest_remote_activation_event(array $request) {
        $timeline = isset($request['timeline']) && is_array($request['timeline']) ? $request['timeline'] : array();
        if (empty($timeline)) {
            return null;
        }

        $viewer_role = (string) ($request['viewer_role'] ?? '');
        $other_domain = $viewer_role === 'requester'
            ? (string) ($request['counterparty_site_domain'] ?? '')
            : (string) ($request['requester_site_domain'] ?? '');
        for ($i = count($timeline) - 1; $i >= 0; $i--) {
            $event = $timeline[$i];
            if (!is_array($event) || (string) ($event['event_type'] ?? '') !== 'link_activated') {
                continue;
            }
            $payload = isset($event['payload']) && is_array($event['payload']) ? $event['payload'] : array();
            if ((string) ($payload['by'] ?? '') === $other_domain) {
                return $event;
            }
        }

        return null;
    }

    private function aysa_exchange_auto_activate_request($project_id, array $request) {
        $status = (string) ($request['status'] ?? '');
        if (in_array($status, array('rejected', 'expired', 'completed', 'disputed'), true)) {
            return array('status' => 'skipped', 'reason' => 'terminal_status');
        }
        if (!$this->aysa_link_exchange_auto_activation_enabled($project_id)) {
            return array('status' => 'skipped', 'reason' => 'auto_activation_disabled');
        }

        $review_started_ts = $this->aysa_exchange_request_review_started_ts($request);
        if ($review_started_ts <= 0 || (time() - $review_started_ts) < ($this->aysa_exchange_auto_activation_review_hours() * HOUR_IN_SECONDS)) {
            return array('status' => 'skipped', 'reason' => 'review_window_open');
        }

        $project_id = intval($project_id);
        $exchange_ref = sanitize_text_field((string) ($request['exchange_ref'] ?? ''));
        $request_id = intval($request['id'] ?? 0);
        $viewer_role = (string) ($request['viewer_role'] ?? '');
        if ($project_id <= 0 || $exchange_ref === '') {
            return array('status' => 'skipped', 'reason' => 'invalid_request');
        }

        if ($status === 'pending_acceptance') {
            $accept_task = $viewer_role === 'counterparty' ? 'exchange_request_respond' : 'exchange_simulate_accept';
            $accept_request = array(
                'task' => $accept_task,
                'project_id' => $project_id,
                'request_id' => $request_id,
                'exchange_ref' => $exchange_ref,
                'decision' => 'accept',
                'counterparty_target_url' => esc_url_raw((string) ($request['counterparty_target_url'] ?? '')),
                'counterparty_anchor_text' => sanitize_text_field((string) ($request['counterparty_anchor_text'] ?? '')),
                'counterparty_notes' => 'Automatically accepted by AYSA Link Exchange after review window.',
                'source' => 'aysa_auto_activation',
                'suppress_billing' => true,
            );
            $accepted = $this->aysa_offpage_async_execute($accept_request);
            if (is_wp_error($accepted)) {
                return array('status' => 'failed', 'reason' => $accepted->get_error_code(), 'message' => $accepted->get_error_message());
            }
            $requester = $this->aysa_get_link_exchange_profile($project_id);
            $reload = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/get', array(
                'project_id' => $project_id,
                'current_site_domain' => $requester['site_domain'],
                'request_id' => $request_id,
                'exchange_ref' => $exchange_ref,
            ), 30);
            if (!is_wp_error($reload) && isset($reload['request']) && is_array($reload['request'])) {
                $request = $reload['request'];
                $viewer_role = (string) ($request['viewer_role'] ?? $viewer_role);
            }
        }

        $obligation = (string) ($request['current_obligation'] ?? '');
        $viewer_owes_activation = ($viewer_role === 'requester' && $obligation === 'requester_activation_required')
            || ($viewer_role === 'counterparty' && $obligation === 'counterparty_activation_required');
        $local_activated = ($viewer_role === 'requester')
            ? ((string) ($request['requester_activation_state'] ?? '') === 'active')
            : ((string) ($request['counterparty_activation_state'] ?? '') === 'active');
        if (!$viewer_owes_activation || $local_activated) {
            return array('status' => 'skipped', 'reason' => 'no_local_activation_due');
        }

        $activated = $this->aysa_offpage_async_execute(array(
            'task' => 'exchange_activate_link',
            'project_id' => $project_id,
            'request_id' => intval($request['id'] ?? $request_id),
            'exchange_ref' => $exchange_ref,
            'source_post_id' => 0,
            'notes' => 'Automatically inserted by AYSA Link Exchange after review window.',
            'suppress_billing' => true,
        ));
        if (is_wp_error($activated)) {
            return array('status' => 'failed', 'reason' => $activated->get_error_code(), 'message' => $activated->get_error_message());
        }

        return array(
            'status' => 'activated',
            'exchange_ref' => $exchange_ref,
            'result' => is_array($activated) ? $activated : array(),
        );
    }

    private function aysa_exchange_score_source_candidate($post, $target_url, $anchor_text) {
        if (!($post instanceof WP_Post)) {
            return array('eligible' => false, 'score' => 0, 'reasons' => array('Invalid source content.'));
        }

        $title = sanitize_text_field((string) get_the_title($post));
        $excerpt = sanitize_text_field(wp_strip_all_tags((string) ($post->post_excerpt ?: '')));
        $content = sanitize_text_field(wp_strip_all_tags((string) $post->post_content));
        $haystack = trim($title . ' ' . $excerpt . ' ' . wp_trim_words($content, 120, ''));

        $anchor_tokens = $this->aysa_exchange_tokenize_for_semantics($anchor_text);
        $target_tokens = $this->aysa_exchange_tokenize_for_semantics($target_url);
        $source_tokens = $this->aysa_exchange_tokenize_for_semantics($haystack);
        $title_tokens = $this->aysa_exchange_tokenize_for_semantics($title);

        $anchor_overlap = array_values(array_intersect($anchor_tokens, $source_tokens));
        $target_overlap = array_values(array_intersect($target_tokens, $source_tokens));
        $title_anchor_overlap = array_values(array_intersect($anchor_tokens, $title_tokens));

        $score = 0;
        $reasons = array();
        if (!empty($title_anchor_overlap)) {
            $score += 35;
            $reasons[] = 'Anchor aligns with the page title.';
        }
        if (!empty($anchor_overlap)) {
            $score += min(35, 10 + (count($anchor_overlap) * 8));
            $reasons[] = 'Anchor terms appear in the source content.';
        } else {
            $reasons[] = 'Anchor terms do not appear in the source content.';
        }
        if (!empty($target_overlap)) {
            $score += min(25, 8 + (count($target_overlap) * 6));
            $reasons[] = 'Target topic overlaps with the source content.';
        } else {
            $reasons[] = 'Target topic is weakly represented in the source content.';
        }
        if ((string) $post->post_type === 'product') {
            $score += 5;
        }

        return array(
            'eligible' => $score >= 35 && (!empty($anchor_overlap) || !empty($title_anchor_overlap)),
            'score' => max(0, min(100, intval($score))),
            'reasons' => $reasons,
        );
    }

    private function aysa_exchange_tokenize_for_semantics($value) {
        $value = strtolower((string) $value);
        $value = preg_replace('#https?://#', ' ', $value);
        $value = preg_replace('#[^a-z0-9ăâîșşțţ]+#iu', ' ', $value);
        $parts = preg_split('/\s+/', trim((string) $value));
        if (!is_array($parts)) {
            return array();
        }

        $tokens = array();
        $stop = array('si', 'și', 'de', 'la', 'in', 'în', 'cu', 'for', 'the', 'and', 'sau', 'ori', 'din', 'pe', 'un', 'una', 'unei', 'o', 'a', 'to');
        foreach ($parts as $part) {
            $part = trim((string) $part);
            if ($part === '' || mb_strlen($part) < 3 || in_array($part, $stop, true)) {
                continue;
            }
            $tokens[$part] = true;
        }
        return array_values(array_keys($tokens));
    }

    private function aysa_exchange_urls_match($left, $right) {
        $left = $this->aysa_exchange_normalize_url_for_exact_match((string) $left);
        $right = $this->aysa_exchange_normalize_url_for_exact_match((string) $right);
        return $left !== '' && $left === $right;
    }

    private function aysa_exchange_normalize_url_for_exact_match($value) {
        $value = trim(html_entity_decode((string) $value, ENT_QUOTES | ENT_HTML5, 'UTF-8'));
        if ($value === '') {
            return '';
        }

        $parts = wp_parse_url($value);
        if (!is_array($parts) || empty($parts['host'])) {
            return rtrim(strtolower($value), '/');
        }

        $scheme = strtolower((string) ($parts['scheme'] ?? 'https'));
        $host = preg_replace('/^www\./i', '', strtolower((string) ($parts['host'] ?? '')));
        if (!is_string($host)) {
            $host = '';
        }
        $path = (string) ($parts['path'] ?? '/');
        $path = '/' . ltrim($path, '/');
        $path = rtrim($path, '/');
        if ($path === '') {
            $path = '/';
        }
        $query = isset($parts['query']) && $parts['query'] !== '' ? '?' . (string) $parts['query'] : '';

        return $scheme . '://' . $host . $path . $query;
    }

    private function aysa_exchange_normalize_human_string($value) {
        $value = trim(html_entity_decode((string) $value, ENT_QUOTES | ENT_HTML5, 'UTF-8'));
        return trim(preg_replace('/\s+/', ' ', $value) ?? $value);
    }

    private function aysa_validate_exchange_request_access($request) {
        if (!is_array($request) || empty($request)) {
            return new WP_Error('exchange_request_missing', 'Exchange request not found.');
        }

        return true;
    }

    private function aysa_get_broker_config() {
        $default_base_url = class_exists('Aysa_API_Config') ? rtrim((string) Aysa_API_Config::get_api_base_url(), '/') : 'https://api.aysa.ai';
        $default_key_id = 'wp_plugin';
        $default_key_secret = '';
        $base_url = defined('AYSA_OAUTH_BROKER_BASE_URL') ? AYSA_OAUTH_BROKER_BASE_URL : $default_base_url;
        $key_id = defined('AYSA_OAUTH_BROKER_KEY_ID') ? AYSA_OAUTH_BROKER_KEY_ID : $default_key_id;
        $key_secret = defined('AYSA_OAUTH_BROKER_KEY_SECRET') ? AYSA_OAUTH_BROKER_KEY_SECRET : $default_key_secret;

        $base_url = rtrim((string) $base_url, '/');
        $key_id = trim((string) $key_id);
        $key_secret = trim((string) $key_secret);

        if ($base_url === '' || $key_id === '' || $key_secret === '') {
            return new WP_Error('broker_config_missing', 'Broker config missing. Set AYSA_OAUTH_BROKER_BASE_URL, AYSA_OAUTH_BROKER_KEY_ID and AYSA_OAUTH_BROKER_KEY_SECRET.');
        }

        return array(
            'base_url' => $base_url,
            'key_id' => $key_id,
            'key_secret' => $key_secret,
        );
    }

    private function aysa_broker_request_async($method, $path, $payload = array(), $timeout = 1) {
        $cfg = $this->aysa_get_broker_config();
        if (is_wp_error($cfg)) {
            return $cfg;
        }

        $method = strtoupper((string) $method);
        $path = $this->aysa_broker_normalize_path((string) $path);
        $url = $cfg['base_url'] . $path;
        $body = ($method === 'POST') ? wp_json_encode($payload) : '';
        if ($body === false) {
            $body = '{}';
        }

        $timestamp = (string) time();
        try {
            $nonce = bin2hex(random_bytes(16));
        } catch (Exception $e) {
            $nonce = wp_generate_password(32, false, false);
        }

        $body_hash = hash('sha256', $body);
        $canonical = implode("\n", array($method, $path, $body_hash, $timestamp, $nonce));
        $signature = hash_hmac('sha256', $canonical, $cfg['key_secret']);

        $args = array(
            'timeout' => max(1, min(10, intval($timeout))),
            'blocking' => false,
            'method' => $method,
            'headers' => array(
                'Content-Type' => 'application/json',
                'X-Aysa-Key' => $cfg['key_id'],
                'X-Aysa-Timestamp' => $timestamp,
                'X-Aysa-Nonce' => $nonce,
                'X-Aysa-Signature' => $signature,
            ),
        );

        if ($method === 'POST') {
            $args['body'] = $body;
        }

        $resp = wp_remote_request($url, $args);
        if (is_wp_error($resp)) {
            return $resp;
        }

        return array('ok' => true);
    }

    private function aysa_should_queue_setup_competition_suggestion($mode, $current_step, $user_message) {
        if ((string) $mode !== 'message' || (string) $current_step !== 'competition') {
            return false;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        if (!empty($_POST['async_competition_suggestion'])) {
            return true;
        }

        $normalized = strtolower(trim((string) $user_message));
        if ($normalized === '') {
            return false;
        }

        if (
            strpos($normalized, 'sugereaza tu') !== false
            || strpos($normalized, 'sugerează tu') !== false
            || strpos($normalized, 'suggest for me') !== false
            || strpos($normalized, 'suggest them for me') !== false
        ) {
            return true;
        }

        return (bool) preg_match('/^(nu stiu|nu știu|i do not know|idk)\b/u', $normalized);
    }

    private function aysa_setup_competition_async_ack($language) {
        $lang = in_array((string) $language, array('ro', 'bg'), true) ? (string) $language : 'en';
        return match ($lang) {
            'ro' => 'Perfect. Acum relaxează-te, bea o cafea sau ieși puțin la aer. Eu pornesc research-ul pentru seed keywords și competitori, lucrez în fundal pentru tine și te anunț când e gata aici în WordPress, iar dacă am emailul disponibil îți trimit și email cu link direct spre plugin.',
            'bg' => 'Чудесно. Сега се отпусни, изпий едно кафе или излез за малко. Аз стартирам research-а за seed keywords и конкуренти, работя във фонов режим за теб и ще те уведомя тук в WordPress, а ако имам имейла ти, ще изпратя и имейл с директен линк към plugin-а.',
            default => 'Perfect. Now relax, grab a coffee, or take a short walk. I am starting the seed keyword and competitor research in the background, I will work on it for you, and I will notify you here in WordPress and by email with a direct plugin link if an email is available.',
        };
    }

    private function aysa_broker_request($method, $path, $payload = array(), $timeout = 30) {
        $cfg = $this->aysa_get_broker_config();
        if (is_wp_error($cfg)) {
            return $cfg;
        }

        $method = strtoupper((string) $method);
        $path = $this->aysa_broker_normalize_path((string) $path);
        $url = $cfg['base_url'] . $path;
        $body = ($method === 'POST') ? wp_json_encode($payload) : '';
        if ($body === false) {
            $body = '{}';
        }

        $timestamp = (string) time();
        try {
            $nonce = bin2hex(random_bytes(16));
        } catch (Exception $e) {
            $nonce = wp_generate_password(32, false, false);
        }

        $body_hash = hash('sha256', $body);
        $canonical = implode("\n", array($method, $path, $body_hash, $timestamp, $nonce));
        $signature = hash_hmac('sha256', $canonical, $cfg['key_secret']);

        $args = array(
            'timeout' => max(10, min(120, intval($timeout))),
            'method' => $method,
            'headers' => array(
                'Content-Type' => 'application/json',
                'X-Aysa-Key' => $cfg['key_id'],
                'X-Aysa-Timestamp' => $timestamp,
                'X-Aysa-Nonce' => $nonce,
                'X-Aysa-Signature' => $signature,
            ),
        );

        if ($method === 'POST') {
            $args['body'] = $body;
        }

        $resp = wp_remote_request($url, $args);
        if (is_wp_error($resp)) {
            return new WP_Error('broker_http_failed', 'Broker request failed: ' . $resp->get_error_message());
        }

        $code = wp_remote_retrieve_response_code($resp);
        $raw_body = (string) wp_remote_retrieve_body($resp);
        $content_type = (string) wp_remote_retrieve_header($resp, 'content-type');
        $decoded = json_decode($raw_body, true);
        if (!is_array($decoded)) {
            $preview = trim(preg_replace('/\s+/', ' ', wp_strip_all_tags($raw_body)));
            if ($preview !== '') {
                $preview = substr($preview, 0, 280);
            }
            $message = 'Broker returned invalid JSON response. [code=' . intval($code) . ']';
            if ($content_type !== '') {
                $message .= ' [content-type=' . $content_type . ']';
            }
            if ($preview !== '') {
                $message .= ' ' . $preview;
            }
            return new WP_Error('broker_invalid_response', $message);
        }

        if ($code < 200 || $code >= 300 || (isset($decoded['ok']) && !$decoded['ok'])) {
            $message = isset($decoded['error']) ? (string) $decoded['error'] : 'Broker returned an error.';
            return new WP_Error('broker_error', $message . ' [code=' . intval($code) . ']');
        }

        $usage = isset($decoded['usage']) && is_array($decoded['usage']) ? $decoded['usage'] : array();
        if (!empty($usage)) {
            $project_id = 0;
            if (is_array($payload) && isset($payload['project_id'])) {
                $project_id = intval($payload['project_id']);
            }
            if ($project_id <= 0) {
                $project_id = intval(get_option('aysa_project_id', 0));
            }

            $action_key = $this->aysa_usage_action_key_from_path($path);
            $reference = ltrim((string) $path, '/');
            $this->aysa_wallet_log_endpoint_usage(
                $project_id,
                (string) $path,
                $usage,
                $action_key,
                $reference,
                array(
                    'http_method' => $method,
                    'response_code' => intval($code),
                )
            );
        }

        return $decoded;
    }

    private function aysa_broker_normalize_path($path) {
        $path = '/' . ltrim((string) $path, '/');
        if ($path !== '/' && substr($path, -1) !== '/') {
            $path .= '/';
        }
        return $path;
    }

    private function aysa_humanize_broker_error($error, $fallback = 'Broker temporarily unavailable. Please retry later.') {
        if (!is_wp_error($error)) {
            return (string) $fallback;
        }

        $code = (string) $error->get_error_code();
        $message = (string) $error->get_error_message();
        if ($code === 'broker_http_failed' && stripos($message, 'cURL error 7') !== false) {
            return 'API broker is temporarily unavailable. Please retry in a moment.';
        }
        if ($code === 'broker_invalid_response') {
            return 'API broker returned an invalid response. Please retry; if it persists, investigate the live API service.';
        }
        if (stripos($message, '/usr/bin/expect') !== false && stripos($message, 'not found') !== false) {
            return 'Site Audit is temporarily unavailable because the API server is missing the crawler dependency. Install the missing package on api.aysa.ai, then retry.';
        }
        if (stripos($message, 'permission denied (publickey)') !== false) {
            return 'Site Audit cannot start because the crawler host rejects the current SSH credentials. Configure valid key-based SSH access for the crawler host, then retry.';
        }

        return ($message !== '') ? $message : (string) $fallback;
    }

    private function aysa_should_use_local_site_audit_fallback($error) {
        if (!is_wp_error($error)) {
            return false;
        }

        $message = strtolower((string) $error->get_error_message());
        if ($message === '') {
            return false;
        }

        $needles = array(
            'permission denied (publickey)',
            '/usr/bin/expect',
            'broker_http_failed',
            'local api broker is offline',
            'failed to start site audit',
            'could not start site audit',
        );

        foreach ($needles as $needle) {
            if (strpos($message, strtolower($needle)) !== false) {
                return true;
            }
        }

        return false;
    }

    private function aysa_local_site_audit_collect_urls($target_url, $limit = 40) {
        $limit = max(10, min(120, intval($limit)));
        $urls = array();
        $home = home_url('/');
        if ($home !== '') {
            $urls[$this->aysa_local_site_audit_normalize_url($home)] = array(
                'url' => $home,
                'title' => get_bloginfo('name'),
            );
        }

        $public_types = get_post_types(array(
            'public' => true,
        ), 'names');
        unset($public_types['attachment']);

        $posts = get_posts(array(
            'post_type' => array_values($public_types),
            'post_status' => 'publish',
            'posts_per_page' => $limit,
            'orderby' => 'date',
            'order' => 'DESC',
            'fields' => 'ids',
            'no_found_rows' => true,
            'suppress_filters' => false,
        ));

        foreach ($posts as $post_id) {
            $url = get_permalink($post_id);
            if (!$url) {
                continue;
            }
            $normalized = $this->aysa_local_site_audit_normalize_url($url);
            if ($normalized === '') {
                continue;
            }
            $urls[$normalized] = array(
                'url' => $url,
                'title' => get_the_title($post_id),
                'post_id' => intval($post_id),
            );
            if (count($urls) >= $limit) {
                break;
            }
        }

        if ($target_url !== '') {
            $normalizedTarget = $this->aysa_local_site_audit_normalize_url($target_url);
            if ($normalizedTarget !== '') {
                $urls = array($normalizedTarget => array(
                    'url' => $target_url,
                    'title' => isset($urls[$normalizedTarget]['title']) ? $urls[$normalizedTarget]['title'] : '',
                )) + $urls;
            }
        }

        return array_values(array_slice($urls, 0, $limit, true));
    }

    private function aysa_local_site_audit_normalize_url($url) {
        $url = trim((string) $url);
        if ($url === '') {
            return '';
        }

        $parts = wp_parse_url($url);
        if (!is_array($parts) || empty($parts['host'])) {
            return '';
        }

        $scheme = !empty($parts['scheme']) ? strtolower((string) $parts['scheme']) : 'https';
        $host = strtolower((string) $parts['host']);
        $path = isset($parts['path']) ? (string) $parts['path'] : '/';
        if ($path === '') {
            $path = '/';
        }
        $path = '/' . ltrim($path, '/');
        if ($path !== '/') {
            $path = untrailingslashit($path);
        }

        return $scheme . '://' . $host . $path;
    }

    private function aysa_local_site_audit_extract_html_metrics($html, $base_url) {
        $metrics = array(
            'title' => '',
            'meta_description' => '',
            'canonical' => '',
            'indexability' => 'Indexable',
            'h1_count' => 0,
            'h2_count' => 0,
            'internal_links' => array(),
        );

        if (!is_string($html) || trim($html) === '') {
            return $metrics;
        }

        if (!class_exists('DOMDocument')) {
            return $metrics;
        }

        $dom = new DOMDocument();
        $previous = libxml_use_internal_errors(true);
        $loaded = $dom->loadHTML($html);
        libxml_clear_errors();
        libxml_use_internal_errors($previous);

        if (!$loaded) {
            return $metrics;
        }

        $titles = $dom->getElementsByTagName('title');
        if ($titles->length > 0) {
            $metrics['title'] = trim((string) $titles->item(0)->textContent);
        }

        $base_host = wp_parse_url($base_url, PHP_URL_HOST);
        $metaTags = $dom->getElementsByTagName('meta');
        foreach ($metaTags as $meta) {
            $name = strtolower(trim((string) $meta->getAttribute('name')));
            $content = trim((string) $meta->getAttribute('content'));
            if ($name === 'description' && $metrics['meta_description'] === '') {
                $metrics['meta_description'] = $content;
            }
            if ($name === 'robots' && stripos($content, 'noindex') !== false) {
                $metrics['indexability'] = 'Non-Indexable';
            }
        }

        $links = $dom->getElementsByTagName('link');
        foreach ($links as $link) {
            $rel = strtolower(trim((string) $link->getAttribute('rel')));
            $href = trim((string) $link->getAttribute('href'));
            if ($rel === 'canonical' && $metrics['canonical'] === '') {
                $metrics['canonical'] = $href;
            }
        }

        $metrics['h1_count'] = $dom->getElementsByTagName('h1')->length;
        $metrics['h2_count'] = $dom->getElementsByTagName('h2')->length;

        $anchors = $dom->getElementsByTagName('a');
        foreach ($anchors as $anchor) {
            $href = trim((string) $anchor->getAttribute('href'));
            if ($href === '' || strpos($href, '#') === 0 || stripos($href, 'mailto:') === 0 || stripos($href, 'tel:') === 0) {
                continue;
            }
            $absolute = $href;
            if (strpos($href, 'http://') !== 0 && strpos($href, 'https://') !== 0) {
                $absolute = home_url('/' . ltrim($href, '/'));
            }
            $absolute = $this->aysa_local_site_audit_normalize_url($absolute);
            if ($absolute === '') {
                continue;
            }
            $host = wp_parse_url($absolute, PHP_URL_HOST);
            if ($base_host && $host && strtolower((string) $host) === strtolower((string) $base_host)) {
                $metrics['internal_links'][] = $absolute;
            }
        }

        $metrics['internal_links'] = array_values(array_unique($metrics['internal_links']));

        return $metrics;
    }

    private function aysa_local_site_audit_build($project_id, $target_url, $job_id) {
        $targets = $this->aysa_local_site_audit_collect_urls($target_url, 40);
        $pages = array();
        $inlinkMap = array();
        $titleCounts = array();
        $metaMissing = array();
        $canonicalMissing = array();
        $multipleH1 = array();
        $errorPages = array();
        $nonIndexable = 0;
        $depthGt3 = 0;

        foreach ($targets as $target) {
            $url = isset($target['url']) ? (string) $target['url'] : '';
            $normalizedUrl = $this->aysa_local_site_audit_normalize_url($url);
            if ($normalizedUrl === '') {
                continue;
            }

            $response = wp_remote_get($url, array(
                'timeout' => 12,
                'redirection' => 3,
                'headers' => array(
                    'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                    'User-Agent' => 'AYSA Site Audit/1.0',
                ),
            ));

            $statusCode = 0;
            $body = '';
            if (!is_wp_error($response)) {
                $statusCode = intval(wp_remote_retrieve_response_code($response));
                $body = (string) wp_remote_retrieve_body($response);
            }

            $metrics = $this->aysa_local_site_audit_extract_html_metrics($body, $url);
            $title = $metrics['title'] !== '' ? $metrics['title'] : (string) ($target['title'] ?? '');
            $indexability = (string) ($metrics['indexability'] ?? 'Indexable');
            $path = (string) wp_parse_url($normalizedUrl, PHP_URL_PATH);
            $depth = 0;
            if ($path !== '' && $path !== '/') {
                $segments = array_values(array_filter(explode('/', trim($path, '/'))));
                $depth = count($segments);
            }

            $pages[$normalizedUrl] = array(
                'url' => $url,
                'title' => $title,
                'status_code' => $statusCode > 0 ? $statusCode : 0,
                'indexability' => $indexability,
                'crawl_depth' => $depth,
                'inlinks' => 0,
            );

            if ($title !== '') {
                $titleKey = strtolower(trim($title));
                $titleCounts[$titleKey] = isset($titleCounts[$titleKey]) ? intval($titleCounts[$titleKey]) + 1 : 1;
            }
            if ($metrics['meta_description'] === '') {
                $metaMissing[] = $normalizedUrl;
            }
            if ($metrics['canonical'] === '') {
                $canonicalMissing[] = $normalizedUrl;
            }
            if (intval($metrics['h1_count']) > 1) {
                $multipleH1[] = $normalizedUrl;
            }
            if ($statusCode >= 400) {
                $errorPages[] = $normalizedUrl;
            }
            if (stripos($indexability, 'non-indexable') !== false) {
                $nonIndexable++;
            }
            if ($depth > 3) {
                $depthGt3++;
            }

            foreach ((array) $metrics['internal_links'] as $linkedUrl) {
                if (!isset($inlinkMap[$linkedUrl])) {
                    $inlinkMap[$linkedUrl] = 0;
                }
                $inlinkMap[$linkedUrl]++;
            }
        }

        $singleInlink = 0;
        $statusBuckets = array(
            '2xx' => 0,
            '3xx' => 0,
            '4xx' => 0,
            '5xx' => 0,
            'other' => 0,
        );
        foreach ($pages as $normalizedUrl => &$page) {
            $page['inlinks'] = intval($inlinkMap[$normalizedUrl] ?? 0);
            if ($page['inlinks'] <= 1) {
                $singleInlink++;
            }
            $status = intval($page['status_code']);
            if ($status >= 200 && $status < 300) {
                $statusBuckets['2xx']++;
            } elseif ($status >= 300 && $status < 400) {
                $statusBuckets['3xx']++;
            } elseif ($status >= 400 && $status < 500) {
                $statusBuckets['4xx']++;
            } elseif ($status >= 500 && $status < 600) {
                $statusBuckets['5xx']++;
            } else {
                $statusBuckets['other']++;
            }
        }
        unset($page);

        $pagesList = array_values($pages);
        $pagesCrawled = count($pagesList);
        $warnings = count($metaMissing) + count($canonicalMissing) + count($multipleH1) + $depthGt3 + $singleInlink;
        $errors = count($errorPages);
        $siteHealth = 100;
        if ($pagesCrawled > 0) {
            $severityScore = ($errors * 3) + ($warnings * 1.5);
            $siteHealth = max(1, intval(round(100 - (($severityScore / max(1, $pagesCrawled)) * 100))));
        }

        $duplicateTitleUrls = array();
        foreach ($pagesList as $page) {
            $titleKey = strtolower(trim((string) ($page['title'] ?? '')));
            if ($titleKey !== '' && intval($titleCounts[$titleKey] ?? 0) > 1) {
                $duplicateTitleUrls[] = (string) ($page['url'] ?? '');
            }
        }

        $issues = array();
        $issues = array_merge($issues, $this->aysa_local_site_audit_issue_from_urls(
            'error',
            'http_status_4xx_5xx',
            'Pages with 4xx/5xx status',
            'Broken or failing URLs found in the local crawl.',
            $errorPages,
            $pagesList
        ));
        $issues = array_merge($issues, $this->aysa_local_site_audit_issue_from_urls(
            'warning',
            'missing_meta_descriptions',
            'Pages missing meta descriptions',
            'Add unique meta descriptions to improve search snippets.',
            $metaMissing,
            $pagesList
        ));
        $issues = array_merge($issues, $this->aysa_local_site_audit_issue_from_urls(
            'warning',
            'missing_canonicals',
            'Pages missing canonical tags',
            'Add canonical tags to clarify the preferred URL.',
            $canonicalMissing,
            $pagesList
        ));
        $issues = array_merge($issues, $this->aysa_local_site_audit_issue_from_urls(
            'notice',
            'multiple_h1',
            'Pages with multiple H1 headings',
            'Review heading structure and keep one primary H1 per page.',
            $multipleH1,
            $pagesList
        ));
        $issues = array_merge($issues, $this->aysa_local_site_audit_issue_from_urls(
            'warning',
            'duplicate_titles',
            'Pages with duplicate titles',
            'Use unique page titles to avoid cannibalization and weak click-through.',
            $duplicateTitleUrls,
            $pagesList
        ));

        $resultFiles = array(
            array('filename' => 'internal-pages-local.csv', 'type' => 'csv', 'row_count' => $pagesCrawled, 'updated_at' => current_time('mysql'), 'size_bytes' => 0),
            array('filename' => 'page-titles-local.csv', 'type' => 'csv', 'row_count' => max(0, count($duplicateTitleUrls)), 'updated_at' => current_time('mysql'), 'size_bytes' => 0),
            array('filename' => 'meta-descriptions-local.csv', 'type' => 'csv', 'row_count' => max(0, count($metaMissing)), 'updated_at' => current_time('mysql'), 'size_bytes' => 0),
            array('filename' => 'canonicals-local.csv', 'type' => 'csv', 'row_count' => max(0, count($canonicalMissing)), 'updated_at' => current_time('mysql'), 'size_bytes' => 0),
            array('filename' => 'directives-local.csv', 'type' => 'csv', 'row_count' => $nonIndexable, 'updated_at' => current_time('mysql'), 'size_bytes' => 0),
            array('filename' => 'links-local.csv', 'type' => 'csv', 'row_count' => $singleInlink, 'updated_at' => current_time('mysql'), 'size_bytes' => 0),
        );

        return array(
            'state' => array(
                'project_id' => intval($project_id),
                'job_id' => (string) $job_id,
                'target_url' => (string) $target_url,
                'status' => 'completed',
                'provider' => 'local_wordpress',
                'started_at' => current_time('mysql'),
                'updated_at' => current_time('mysql'),
                'result_count' => count($resultFiles),
                'job_dir' => 'local-wordpress://site-audit/' . sanitize_key((string) $job_id),
                'latest_output_dir' => 'local-wordpress://site-audit/' . sanitize_key((string) $job_id),
                'audit_snapshot' => array(
                    'overview' => array(
                        'pages_crawled' => $pagesCrawled,
                        'site_health' => $siteHealth,
                        'errors' => $errors,
                        'warnings' => $warnings,
                        'notices' => max(0, count($issues) - $errors),
                        'status_codes' => $statusBuckets,
                        'indexable_pages' => max(0, $pagesCrawled - $nonIndexable),
                        'non_indexable_pages' => $nonIndexable,
                        'deep_pages_over_3' => $depthGt3,
                        'single_inlink_pages' => $singleInlink,
                    ),
                    'issues' => array_values($issues),
                    'pages' => array_values($pagesList),
                    'statistics' => array(
                        'exports' => array(
                            'internal' => $pagesCrawled,
                            'page_titles' => count($duplicateTitleUrls),
                            'meta_descriptions' => count($metaMissing),
                            'canonicals' => count($canonicalMissing),
                            'directives' => $nonIndexable,
                            'links' => $singleInlink,
                        ),
                    ),
                ),
                'result_files' => $resultFiles,
            ),
        );
    }

    private function aysa_local_site_audit_issue_from_urls($severity, $key, $title, $description, $urls, $pagesList) {
        $urls = array_values(array_unique(array_filter(array_map('strval', (array) $urls))));
        if (empty($urls)) {
            return array();
        }

        $pageMap = array();
        foreach ((array) $pagesList as $page) {
            if (!is_array($page) || empty($page['url'])) {
                continue;
            }
            $pageMap[$this->aysa_local_site_audit_normalize_url((string) $page['url'])] = $page;
        }

        $samples = array();
        foreach ($urls as $url) {
            $normalized = $this->aysa_local_site_audit_normalize_url($url);
            $page = isset($pageMap[$normalized]) ? $pageMap[$normalized] : array('url' => $url);
            $samples[] = array(
                'url' => (string) ($page['url'] ?? $url),
                'title' => (string) ($page['title'] ?? ''),
                'status_code' => intval($page['status_code'] ?? 0),
                'indexability' => (string) ($page['indexability'] ?? ''),
                'crawl_depth' => intval($page['crawl_depth'] ?? 0),
                'inlinks' => intval($page['inlinks'] ?? 0),
            );
            if (count($samples) >= 80) {
                break;
            }
        }

        return array(
            array(
                'severity' => sanitize_key((string) $severity),
                'key' => sanitize_key((string) $key),
                'title' => sanitize_text_field((string) $title),
                'affected_pages' => count($urls),
                'description' => sanitize_text_field((string) $description),
                'details' => array(
                    'source' => 'local_wordpress',
                    'filename' => sanitize_key((string) $key) . '.csv',
                    'sample_rows' => $samples,
                    'sample_columns' => array('url', 'title', 'status_code', 'indexability', 'crawl_depth', 'inlinks'),
                ),
            ),
        );
    }

    private function aysa_technical_site_audit_state_key($project_id) {
        return 'aysa_technical_site_audit_state_' . intval($project_id);
    }

    private function aysa_technical_site_audit_issue_lifecycle_key($project_id) {
        return 'aysa_technical_site_audit_issue_lifecycle_' . intval($project_id);
    }

    private function aysa_technical_site_audit_issue_id($key, $title, $category) {
        $base = sanitize_key((string) $key);
        if ($base === '') {
            $base = sanitize_key((string) $category . '_' . substr(md5((string) $title), 0, 10));
        }
        return $base;
    }

    private function aysa_technical_site_audit_get_issue_lifecycle_map($project_id) {
        $project_id = $this->aysa_resolve_technical_project_id(intval($project_id));
        $state = get_option($this->aysa_technical_site_audit_issue_lifecycle_key($project_id), array());
        return is_array($state) ? $state : array();
    }

    private function aysa_technical_site_audit_update_issue_lifecycle($project_id, $issue_id, $lifecycle_state) {
        $project_id = $this->aysa_resolve_technical_project_id(intval($project_id));
        $issue_id = sanitize_text_field((string) $issue_id);
        $lifecycle_state = sanitize_key((string) $lifecycle_state);
        if ($project_id <= 0 || $issue_id === '') {
            return array('applied' => false, 'reason' => 'invalid_issue_lifecycle_context');
        }
        if (!in_array($lifecycle_state, array('open', 'accepted', 'ignored', 'resolved', 'verified'), true)) {
            return array('applied' => false, 'reason' => 'invalid_lifecycle_state');
        }

        $state = $this->aysa_technical_site_audit_get_issue_lifecycle_map($project_id);
        if ($lifecycle_state === 'open') {
            unset($state[$issue_id]);
        } else {
            $state[$issue_id] = array(
                'state' => $lifecycle_state,
                'updated_at' => current_time('mysql'),
                'updated_at_unix' => time(),
            );
        }
        update_option($this->aysa_technical_site_audit_issue_lifecycle_key($project_id), $state, false);

        return array(
            'applied' => true,
            'issue_id' => $issue_id,
            'lifecycle_state' => $lifecycle_state,
        );
    }

    private function aysa_resolve_technical_project_id($project_id = 0, $site_host = '') {
        // Keep the historical site_host argument for internal caller compatibility,
        // but project authority always comes from this WordPress installation.
        unset($site_host);
        return $this->aysa_resolve_installed_project_id(intval($project_id));
    }

    private function aysa_technical_site_audit_snapshot_has_data($snapshot) {
        if (!is_array($snapshot) || empty($snapshot)) {
            return false;
        }

        $overview_rows = isset($snapshot['overview_rows']) && is_array($snapshot['overview_rows']) ? $snapshot['overview_rows'] : array();
        $issue_rows = isset($snapshot['issue_rows']) && is_array($snapshot['issue_rows'])
            ? $snapshot['issue_rows']
            : (isset($snapshot['rows']) && is_array($snapshot['rows']) ? $snapshot['rows'] : array());
        $page_rows = isset($snapshot['page_rows']) && is_array($snapshot['page_rows'])
            ? $snapshot['page_rows']
            : (isset($snapshot['pages']) && is_array($snapshot['pages']) ? $snapshot['pages'] : array());
        $file_rows = isset($snapshot['file_rows']) && is_array($snapshot['file_rows'])
            ? $snapshot['file_rows']
            : (isset($snapshot['files']) && is_array($snapshot['files']) ? $snapshot['files'] : array());

        return $overview_rows !== array()
            || $issue_rows !== array()
            || $page_rows !== array()
            || $file_rows !== array()
            || intval($snapshot['pages_crawled'] ?? 0) > 0
            || intval($snapshot['count'] ?? 0) > 0;
    }

    private function aysa_technical_site_audit_merge_local_state($project_id, $incoming_state, $reset_local_fields = false) {
        $project_id = $this->aysa_resolve_technical_project_id(intval($project_id));
        $incoming_state = is_array($incoming_state) ? $incoming_state : array();
        if ($project_id <= 0) {
            return $incoming_state;
        }

        if ($reset_local_fields) {
            unset($incoming_state['action_bundles']);
            return $incoming_state;
        }

        $current_state = get_option($this->aysa_technical_site_audit_state_key($project_id), array());
        if (!is_array($current_state) || empty($current_state)) {
            $latest_version = $this->aysa_sf_site_audit_get_latest_version_row($project_id);
            if (is_array($latest_version) && !empty($latest_version)) {
                $current_state = $this->aysa_sf_site_audit_build_state_from_version_row($latest_version);
            }
        }
        if (!is_array($current_state) || empty($current_state)) {
            return $incoming_state;
        }

        if (isset($current_state['action_bundles']) && is_array($current_state['action_bundles']) && !empty($current_state['action_bundles'])) {
            $incoming_state['action_bundles'] = $current_state['action_bundles'];
        }

        return $incoming_state;
    }

    public function aysa_sf_site_audit_versions_maybe_create_table() {
        $this->aysa_sf_site_audit_versions_create_table();
    }

    private function aysa_sf_site_audit_versions_table_name() {
        global $wpdb;
        return $wpdb->prefix . 'aysa_sf_site_audit_versions';
    }

    private function aysa_sf_site_audit_versions_create_table() {
        global $wpdb;

        static $ready = false;
        if ($ready) {
            return;
        }

        $table = esc_sql($this->aysa_sf_site_audit_versions_table_name());
        $charset_collate = $wpdb->get_charset_collate();
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';

        $sql = "CREATE TABLE IF NOT EXISTS {$table} (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            project_id BIGINT UNSIGNED NOT NULL,
            version_number INT UNSIGNED NOT NULL,
            job_id VARCHAR(191) NOT NULL,
            mode VARCHAR(64) NOT NULL DEFAULT 'baseline_full',
            provider VARCHAR(64) NOT NULL DEFAULT 'screamingfrog',
            status VARCHAR(32) NOT NULL DEFAULT 'completed',
            target_url TEXT NULL,
            job_dir TEXT NULL,
            latest_output_dir TEXT NULL,
            result_count INT UNSIGNED NOT NULL DEFAULT 0,
            pages_crawled INT UNSIGNED NOT NULL DEFAULT 0,
            issue_count INT UNSIGNED NOT NULL DEFAULT 0,
            site_health INT UNSIGNED NOT NULL DEFAULT 0,
            snapshot_json LONGTEXT NOT NULL,
            files_json LONGTEXT NOT NULL,
            state_json LONGTEXT NOT NULL,
            created_at DATETIME NOT NULL,
            updated_at DATETIME NOT NULL,
            completed_at DATETIME NULL,
            PRIMARY KEY (id),
            UNIQUE KEY project_version_unique (project_id, version_number),
            UNIQUE KEY project_job_unique (project_id, job_id),
            KEY project_id_idx (project_id),
            KEY status_idx (status),
            KEY completed_at_idx (completed_at),
            KEY updated_at_idx (updated_at)
        ) {$charset_collate};";

        dbDelta($sql);
        $ready = true;
    }

    private function aysa_sf_site_audit_version_history_count($project_id) {
        global $wpdb;

        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return 0;
        }

        $this->aysa_sf_site_audit_versions_create_table();
        $table = esc_sql($this->aysa_sf_site_audit_versions_table_name());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if ($exists !== $table) {
            return 0;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        return intval($wpdb->get_var($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT COUNT(*) FROM {$table} WHERE project_id = %d",
            $project_id
        )));
    }

    private function aysa_sf_site_audit_get_latest_version_row($project_id) {
        global $wpdb;

        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return null;
        }

        $this->aysa_sf_site_audit_versions_create_table();
        $table = esc_sql($this->aysa_sf_site_audit_versions_table_name());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if ($exists !== $table) {
            return null;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $row = $wpdb->get_row($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT * FROM {$table} WHERE project_id = %d ORDER BY version_number DESC LIMIT 1",
            $project_id
        ), ARRAY_A);

        return is_array($row) ? $row : null;
    }

    private function aysa_sf_site_audit_decode_json_field($value, $default = array()) {
        if (!is_string($value) || $value === '') {
            return $default;
        }
        $decoded = json_decode($value, true);
        return is_array($decoded) ? $decoded : $default;
    }

    private function aysa_sf_site_audit_build_state_from_version_row($row) {
        if (!is_array($row) || empty($row)) {
            return array();
        }

        $state = $this->aysa_sf_site_audit_decode_json_field($row['state_json'] ?? '', array());
        $audit = $this->aysa_sf_site_audit_decode_json_field($row['snapshot_json'] ?? '', array());
        $files = $this->aysa_sf_site_audit_decode_json_field($row['files_json'] ?? '', array());

        $state['project_id'] = intval($row['project_id'] ?? ($state['project_id'] ?? 0));
        $state['job_id'] = (string) ($row['job_id'] ?? ($state['job_id'] ?? ''));
        $state['status'] = (string) ($row['status'] ?? ($state['status'] ?? 'completed'));
        $state['mode'] = (string) ($row['mode'] ?? ($state['mode'] ?? 'baseline_full'));
        $state['provider'] = (string) ($row['provider'] ?? ($state['provider'] ?? 'screamingfrog'));
        $state['target_url'] = (string) ($row['target_url'] ?? ($state['target_url'] ?? ''));
        $state['job_dir'] = (string) ($row['job_dir'] ?? ($state['job_dir'] ?? ''));
        $state['latest_output_dir'] = (string) ($row['latest_output_dir'] ?? ($state['latest_output_dir'] ?? ''));
        $state['result_count'] = intval($row['result_count'] ?? ($state['result_count'] ?? 0));
        $state['audit_snapshot'] = $audit;
        $state['result_files'] = is_array($files) ? array_values($files) : array();
        $state['version_id'] = intval($row['id'] ?? 0);
        $state['version_number'] = intval($row['version_number'] ?? 0);
        $state['version_created_at'] = (string) ($row['created_at'] ?? '');
        $state['version_completed_at'] = (string) ($row['completed_at'] ?? '');

        return $state;
    }

    private function aysa_sf_site_audit_persist_version($project_id, $state) {
        global $wpdb;

        $project_id = intval($project_id);
        if ($project_id <= 0 || !is_array($state) || empty($state)) {
            return 0;
        }

        $job_id = sanitize_text_field((string) ($state['job_id'] ?? ''));
        if ($job_id === '') {
            return 0;
        }

        $audit = !empty($state['audit_snapshot']) && is_array($state['audit_snapshot']) ? $state['audit_snapshot'] : array();
        $files = !empty($state['result_files']) && is_array($state['result_files']) ? array_values($state['result_files']) : array();
        if (empty($audit) && empty($files)) {
            return 0;
        }

        $this->aysa_sf_site_audit_versions_create_table();
        $table = esc_sql($this->aysa_sf_site_audit_versions_table_name());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if ($exists !== $table) {
            return 0;
        }

        $overview = !empty($audit['overview']) && is_array($audit['overview']) ? $audit['overview'] : array();
        $created_at = current_time('mysql');
        $completed_at = null;
        $status = sanitize_key((string) ($state['status'] ?? 'completed'));
        if ($status === 'completed') {
            $completed_at = current_time('mysql');
        }

        $snapshot_json = wp_json_encode($audit);
        $files_json = wp_json_encode($files);
        $state_json = wp_json_encode($state);
        if ($snapshot_json === false || $files_json === false || $state_json === false) {
            return 0;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $existing = $wpdb->get_row($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT id, version_number, created_at, completed_at FROM {$table} WHERE project_id = %d AND job_id = %s LIMIT 1",
            $project_id,
            $job_id
        ), ARRAY_A);

        if (is_array($existing) && !empty($existing['id'])) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->update(
                $table,
                array(
                    'mode' => sanitize_text_field((string) ($state['mode'] ?? 'baseline_full')),
                    'provider' => sanitize_text_field((string) ($state['provider'] ?? 'screamingfrog')),
                    'status' => $status !== '' ? $status : 'completed',
                    'target_url' => esc_url_raw((string) ($state['target_url'] ?? '')),
                    'job_dir' => sanitize_text_field((string) ($state['job_dir'] ?? '')),
                    'latest_output_dir' => sanitize_text_field((string) ($state['latest_output_dir'] ?? '')),
                    'result_count' => count($files),
                    'pages_crawled' => intval($overview['pages_crawled'] ?? 0),
                    'issue_count' => count(!empty($audit['issues']) && is_array($audit['issues']) ? $audit['issues'] : array()),
                    'site_health' => intval($overview['site_health'] ?? 0),
                    'snapshot_json' => $snapshot_json,
                    'files_json' => $files_json,
                    'state_json' => $state_json,
                    'updated_at' => current_time('mysql'),
                    'completed_at' => $completed_at ?: ($existing['completed_at'] ?? null),
                ),
                array('id' => intval($existing['id'])),
                array('%s','%s','%s','%s','%s','%s','%d','%d','%d','%d','%s','%s','%s','%s','%s'),
                array('%d')
            );

            return intval($existing['id']);
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $next_version = intval($wpdb->get_var($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT COALESCE(MAX(version_number), 0) + 1 FROM {$table} WHERE project_id = %d",
            $project_id
        )));
        if ($next_version <= 0) {
            $next_version = 1;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $wpdb->insert(
            $table,
            array(
                'project_id' => $project_id,
                'version_number' => $next_version,
                'job_id' => $job_id,
                'mode' => sanitize_text_field((string) ($state['mode'] ?? 'baseline_full')),
                'provider' => sanitize_text_field((string) ($state['provider'] ?? 'screamingfrog')),
                'status' => $status !== '' ? $status : 'completed',
                'target_url' => esc_url_raw((string) ($state['target_url'] ?? '')),
                'job_dir' => sanitize_text_field((string) ($state['job_dir'] ?? '')),
                'latest_output_dir' => sanitize_text_field((string) ($state['latest_output_dir'] ?? '')),
                'result_count' => count($files),
                'pages_crawled' => intval($overview['pages_crawled'] ?? 0),
                'issue_count' => count(!empty($audit['issues']) && is_array($audit['issues']) ? $audit['issues'] : array()),
                'site_health' => intval($overview['site_health'] ?? 0),
                'snapshot_json' => $snapshot_json,
                'files_json' => $files_json,
                'state_json' => $state_json,
                'created_at' => $created_at,
                'updated_at' => $created_at,
                'completed_at' => $completed_at,
            ),
            array('%d','%d','%s','%s','%s','%s','%s','%s','%s','%d','%d','%d','%d','%s','%s','%s','%s','%s','%s')
        );

        return intval($wpdb->insert_id);
    }

    private function aysa_adjustments_build_technical_site_audit_conclusion($project_id, $state, $overview, $issues, $pages, $files, $statistics, $history_count, $recommended_actions = array()) {
        $project_id = intval($project_id);
        $version_number = intval($state['version_number'] ?? 0);
        $pages_crawled = intval($overview['pages_crawled'] ?? 0);
        $site_health = intval($overview['site_health'] ?? 0);
        $errors = intval($overview['errors'] ?? 0);
        $warnings = intval($overview['warnings'] ?? 0);
        $indexable_pages = intval($overview['indexable_pages'] ?? 0);
        $non_indexable_pages = intval($overview['non_indexable_pages'] ?? 0);
        $deep_pages = intval($overview['deep_pages_over_3'] ?? 0);
        $thin_linking_pages = intval($overview['single_inlink_pages'] ?? 0);
        $status_codes = !empty($overview['status_codes']) && is_array($overview['status_codes']) ? $overview['status_codes'] : array();
        $status_4xx = intval($status_codes['4xx'] ?? 0);
        $status_5xx = intval($status_codes['5xx'] ?? 0);
        $artifact_count = count(is_array($files) ? $files : array());
        $issue_count = count(is_array($issues) ? $issues : array());
        $mode = sanitize_text_field((string) ($state['mode'] ?? 'baseline_full'));
        $updated_at = sanitize_text_field((string) ($state['updated_at'] ?? ''));
        $version_label = $version_number > 0 ? ('v' . $version_number) : 'current';

        $normalized_issues = is_array($issues) ? array_values($issues) : array();
        usort($normalized_issues, function($left, $right) {
            $left_rank = intval($left['priority_rank'] ?? 99);
            $right_rank = intval($right['priority_rank'] ?? 99);
            if ($left_rank !== $right_rank) {
                return ($left_rank < $right_rank) ? -1 : 1;
            }
            $left_count = intval($left['affected_pages'] ?? 0);
            $right_count = intval($right['affected_pages'] ?? 0);
            if ($left_count === $right_count) {
                return 0;
            }
            return ($left_count > $right_count) ? -1 : 1;
        });

        $top_issue = array();
        if (is_array($issues) && !empty($issues)) {
            $top_issue = is_array($normalized_issues[0] ?? null) ? $normalized_issues[0] : array();
        }

        $status = 'healthy';
        if ($status_5xx > 0 || $errors > 0) {
            $status = 'critical';
        } elseif ($status_4xx > 0 || $warnings > 0 || $issue_count > 0 || $deep_pages > 0 || $thin_linking_pages > 0) {
            $status = 'attention';
        }

        $status_label_map = array(
            'healthy' => 'Healthy',
            'attention' => 'Needs attention',
            'critical' => 'Critical',
        );
        $status_label = $status_label_map[$status] ?? 'Needs attention';

        $main_problem = '';
        if (!empty($top_issue['title_plain'])) {
            $main_problem = sanitize_text_field((string) $top_issue['title_plain']);
        } elseif ($pages_crawled <= 0) {
            $main_problem = 'The plugin does not have enough crawl data loaded yet to explain the baseline clearly.';
        } else {
            $main_problem = 'The current crawl shows technical issues that need prioritised cleanup before the next remediation pass.';
        }

        if ($status === 'critical') {
            $business_impact = 'Critical crawl or indexing failures can stop important pages from being accessed or ranked.';
        } elseif (!empty($top_issue['impact_plain'])) {
            $business_impact = sanitize_text_field((string) $top_issue['impact_plain']);
        } elseif ($pages_crawled > 0) {
            $business_impact = 'The current issues reduce crawl efficiency or search visibility across the site.';
        } else {
            $business_impact = 'Load the audit data first so the plugin can explain the real SEO impact.';
        }

        $summary = sprintf(
            '%s: %s',
            $status_label,
            $main_problem
        );

        $highlights = array();
        if ($artifact_count > 0) {
            $highlights[] = sprintf(
                '%d audit artifacts are stored for %s and ready for review.',
                $artifact_count,
                $version_label
            );
        }
        if ($indexable_pages > 0 || $non_indexable_pages > 0) {
            $highlights[] = sprintf(
                'Indexability split: %d indexable pages and %d non-indexable pages.',
                $indexable_pages,
                $non_indexable_pages
            );
        }
        if (!empty($top_issue['title_plain'])) {
            $highlights[] = sprintf(
                'Top issue: %s (%d affected pages).',
                sanitize_text_field((string) $top_issue['title_plain']),
                intval($top_issue['affected_pages'] ?? 0)
            );
        }
        if ($deep_pages > 0) {
            $highlights[] = sprintf('%d pages sit deeper than crawl depth 3.', $deep_pages);
        }
        if ($thin_linking_pages > 0) {
            $highlights[] = sprintf('%d pages currently have one or fewer inlinks.', $thin_linking_pages);
        }
        if ($updated_at !== '') {
            $highlights[] = sprintf('Last synced in plugin on %s.', $updated_at);
        }

        $priority_actions = array();
        if ($status_5xx > 0 || $errors > 0) {
            $priority_actions[] = sprintf('Investigate %d critical crawl/server issues before using this baseline operationally.', max($status_5xx, $errors));
        }
        if ($status_4xx > 0) {
            $priority_actions[] = sprintf('Review %d internal URLs returning 4xx and remove or repair their internal links.', $status_4xx);
        }
        if (!empty($top_issue['first_action_plain'])) {
            $priority_actions[] = sanitize_text_field((string) $top_issue['first_action_plain']);
        }
        if ($deep_pages > 0) {
            $priority_actions[] = 'Flatten internal architecture for deeper URLs so key commercial pages are reachable within three clicks.';
        }
        if ($thin_linking_pages > 0) {
            $priority_actions[] = 'Strengthen internal linking for pages with weak inlink support before the next crawl comparison.';
        }
        if (empty($priority_actions)) {
            $priority_actions[] = 'Use this baseline as the comparison point for the next remediation crawl and track deltas by version.';
        }
        $priority_actions = array_values(array_slice(array_unique($priority_actions), 0, 4));
        $what_to_do_first = array_values(array_slice($priority_actions, 0, 3));

        $top_issue_cards = array();
        foreach (array_slice($normalized_issues, 0, 3) as $issue) {
            $top_issue_cards[] = array(
                'title' => sanitize_text_field((string) ($issue['title_plain'] ?? $issue['title'] ?? '')),
                'priority_label' => sanitize_text_field((string) ($issue['priority_label'] ?? 'Medium')),
                'affected_pages' => intval($issue['affected_pages'] ?? 0),
                'affected_display' => sanitize_text_field((string) ($issue['affected_display'] ?? '0 pages')),
                'impact_plain' => sanitize_text_field((string) ($issue['impact_plain'] ?? 'This can limit crawl quality or search visibility.')),
                'first_action_plain' => sanitize_text_field((string) ($issue['first_action_plain'] ?? 'Open details and review the affected URLs first.')),
            );
        }

        return array(
            'status' => $status,
            'status_label' => $status_label,
            'summary' => sanitize_text_field($summary),
            'main_problem' => sanitize_text_field($main_problem),
            'business_impact' => sanitize_text_field($business_impact),
            'highlights' => array_values(array_slice(array_map('sanitize_text_field', $highlights), 0, 6)),
            'priority_actions' => array_values(array_map('sanitize_text_field', $priority_actions)),
            'what_to_do_first' => array_values(array_map('sanitize_text_field', $what_to_do_first)),
            'top_issues' => $top_issue_cards,
            'recommended_actions' => is_array($recommended_actions) ? array_values($recommended_actions) : array(),
            'version_label' => sanitize_text_field($version_label),
            'history_count' => max(0, intval($history_count)),
            'artifact_count' => $artifact_count,
            'mode' => $mode,
            'project_id' => $project_id,
            'updated_at' => $updated_at,
        );
    }

    private function aysa_technical_site_audit_start_queued($payload) {
        $project_id = intval($payload['project_id'] ?? 0);
        $target_url = isset($payload['url']) ? esc_url_raw((string) $payload['url']) : '';
        if ($project_id <= 0) {
            return new WP_Error('invalid_technical_site_audit_project', 'Invalid project id.');
        }
        if ($target_url === '') {
            $target_url = $this->aysa_technical_resolve_target_url();
        }

        $broker_payload = array(
            'project_id' => $project_id,
            'url' => $target_url,
            'job_id' => isset($payload['job_id']) ? sanitize_text_field((string) $payload['job_id']) : ('wp_' . $project_id . '_' . gmdate('Ymd_His')),
            'mode' => (string) ($payload['mode'] ?? 'baseline_full'),
        );

        $result = $this->aysa_broker_request('POST', '/v1/technical/screamingfrog/start', $broker_payload, 90);
        if (is_wp_error($result)) {
            return $result;
        }
        if (!is_array($result) || empty($result['ok'])) {
            return new WP_Error('technical_site_audit_start_failed', is_array($result) ? (string) ($result['error'] ?? 'technical_audit_start_failed') : 'technical_audit_start_failed');
        }

        $state = array(
            'project_id' => $project_id,
            'job_id' => (string) ($result['job_id'] ?? ''),
            'target_url' => (string) ($result['target_url'] ?? $target_url),
            'status' => 'running',
            'started_at' => current_time('mysql'),
            'updated_at' => current_time('mysql'),
            'mode' => (string) ($result['mode'] ?? 'baseline_full'),
            'config_path' => (string) ($result['config_path'] ?? ''),
            'export_tabs' => (string) ($result['export_tabs'] ?? ''),
            'bulk_exports' => (string) ($result['bulk_exports'] ?? ''),
            'reports' => (string) ($result['reports'] ?? ''),
            'result_count' => 0,
            'billing_strategy' => 'site_audit_fixed_500_v1',
            'billing_status' => 'pending',
        );
        $state = $this->aysa_technical_site_audit_merge_local_state($project_id, $state, true);
        update_option($this->aysa_technical_site_audit_state_key($project_id), $state, false);

        return array(
            'status' => 'running',
            'worker_ref' => $this->aysa_project_jobs_make_worker_ref('technical_site_audit', 'site_audit', (string) ($state['job_id'] ?? '')),
            'worker_status' => 'running',
            'result_summary' => array(),
            'started_at' => time(),
        );
    }

    private function aysa_technical_pagespeed_state_key($project_id) {
        return 'aysa_technical_pagespeed_state_' . intval($project_id);
    }

    private function aysa_technical_schema_state_key($project_id) {
        return 'aysa_technical_schema_state_' . intval($project_id);
    }

    private function aysa_technical_redirects_state_key($project_id) {
        return 'aysa_technical_redirects_state_' . intval($project_id);
    }

    private function aysa_technical_sitemap_robots_state_key($project_id) {
        return 'aysa_technical_sitemap_robots_state_' . intval($project_id);
    }

    private function aysa_technical_sitemap_generation_state_key() {
        return 'aysa_technical_sitemap_generation_state';
    }

    private function aysa_technical_internal_linking_preview_state_key($project_id) {
        return 'aysa_technical_internal_linking_preview_state_' . intval($project_id);
    }

    private function aysa_technical_robots_file_path() {
        return trailingslashit(ABSPATH) . 'robots.txt';
    }

    private function aysa_technical_sitemap_file_path() {
        return trailingslashit(ABSPATH) . 'sitemap_index.xml';
    }

    private function aysa_technical_preferred_sitemap_url($target_url = '') {
        $base = trim((string) $target_url);
        if ($base === '') {
            $base = home_url('/');
        }
        return untrailingslashit($base) . '/sitemap_index.xml';
    }

    private function aysa_technical_fetch_sitemap_candidates($target_url) {
        $target_url = untrailingslashit((string) $target_url);
        $candidates = array(
            array(
                'url' => $this->aysa_technical_preferred_sitemap_url($target_url),
                'provider' => 'aysa',
                'provider_label' => 'AYSA Sitemap',
            ),
            array(
                'url' => $target_url . '/sitemap.xml',
                'provider' => 'legacy_sitemap',
                'provider_label' => 'Compatibility sitemap',
            ),
            array(
                'url' => $target_url . '/wp-sitemap.xml',
                'provider' => 'wp_core_sitemap',
                'provider_label' => 'Core sitemap endpoint',
            ),
        );
        $rows = array();
        foreach ($candidates as $candidate) {
            $candidate_url = (string) ($candidate['url'] ?? '');
            $resp = wp_remote_get($candidate_url, array(
                'timeout' => 4,
                'redirection' => 0,
                'headers' => array(
                    'Accept' => 'application/xml,text/xml,*/*',
                ),
            ));
            if (is_wp_error($resp)) {
                $rows[] = array(
                    'url' => $candidate_url,
                    'provider' => (string) ($candidate['provider'] ?? ''),
                    'provider_label' => (string) ($candidate['provider_label'] ?? ''),
                    'reachable' => false,
                    'status_code' => 0,
                    'entries_count' => '',
                    'type' => '',
                    'message' => $resp->get_error_message(),
                );
                continue;
            }
            $status_code = intval(wp_remote_retrieve_response_code($resp));
            $body = (string) wp_remote_retrieve_body($resp);
            $location = (string) wp_remote_retrieve_header($resp, 'location');
            $is_reachable = ($status_code >= 200 && $status_code < 300);
            $is_xml = (stripos($body, '<urlset') !== false) || (stripos($body, '<sitemapindex') !== false) || (stripos($body, '<?xml') !== false);
            $entry_count = '';
            $map_type = '';
            if ($is_xml && $is_reachable) {
                if (stripos($body, '<urlset') !== false) {
                    $map_type = 'urlset';
                    $entry_count = substr_count(strtolower($body), '<url>');
                } elseif (stripos($body, '<sitemapindex') !== false) {
                    $map_type = 'index';
                    $entry_count = substr_count(strtolower($body), '<sitemap>');
                }
            } elseif ($status_code >= 300 && $status_code < 400 && $location !== '') {
                $map_type = 'redirect';
            }
            $rows[] = array(
                'url' => $candidate_url,
                'provider' => (string) ($candidate['provider'] ?? ''),
                'provider_label' => (string) ($candidate['provider_label'] ?? ''),
                'reachable' => ($is_reachable && $is_xml),
                'status_code' => $status_code,
                'entries_count' => $entry_count,
                'type' => $map_type,
                'location' => $location,
                'message' => ($status_code >= 300 && $status_code < 400 && $location !== '')
                    ? ('Redirects to ' . $location)
                    : ($is_xml ? '' : 'Endpoint is reachable but not valid XML sitemap.'),
            );
        }
        return $rows;
    }

    private function aysa_technical_sitemap_inventory_bounds() {
        return array(
            'max_child_sitemaps_inspected' => 50,
            'max_urls_parsed' => 20000,
            'max_url_sample' => 100,
            'max_body_bytes' => 2097152,
        );
    }

    private function aysa_technical_sitemap_extract_loc_values($body) {
        $body = (string) $body;
        if ($body === '') {
            return array();
        }
        $matches = array();
        if (!preg_match_all('/<loc[^>]*>\s*(.*?)\s*<\/loc>/is', $body, $matches)) {
            return array();
        }
        $values = array();
        foreach ((array) ($matches[1] ?? array()) as $loc) {
            $loc = html_entity_decode(wp_strip_all_tags((string) $loc), ENT_QUOTES, 'UTF-8');
            $loc = esc_url_raw(trim($loc));
            if ($loc !== '') {
                $values[] = $loc;
            }
        }
        return array_values(array_unique($values));
    }

    private function aysa_technical_sitemap_same_host($base_url, $candidate_url) {
        $base_host = strtolower((string) wp_parse_url((string) $base_url, PHP_URL_HOST));
        $candidate_host = strtolower((string) wp_parse_url((string) $candidate_url, PHP_URL_HOST));
        return $base_host !== '' && $candidate_host !== '' && $base_host === $candidate_host;
    }

    private function aysa_technical_parse_sitemap_inventory($sitemap_url, $target_url = '', $depth = 0, &$seen = array(), &$urls = array(), &$children = array(), &$errors = array()) {
        $bounds = $this->aysa_technical_sitemap_inventory_bounds();
        $sitemap_url = esc_url_raw((string) $sitemap_url);
        if ($sitemap_url === '' || isset($seen[$sitemap_url])) {
            return;
        }
        if (count($urls) >= intval($bounds['max_urls_parsed'])) {
            return;
        }
        if ($target_url !== '' && !$this->aysa_technical_sitemap_same_host($target_url, $sitemap_url)) {
            $errors[] = 'cross_host_sitemap_skipped';
            return;
        }
        $seen[$sitemap_url] = true;

        $resp = wp_remote_get($sitemap_url, array(
            'timeout' => 4,
            'redirection' => 2,
            'limit_response_size' => intval($bounds['max_body_bytes']),
            'headers' => array(
                'Accept' => 'application/xml,text/xml,*/*',
            ),
        ));
        if (is_wp_error($resp)) {
            $errors[] = 'fetch_error:' . $resp->get_error_code();
            return;
        }

        $status_code = intval(wp_remote_retrieve_response_code($resp));
        $body = (string) wp_remote_retrieve_body($resp);
        if ($status_code < 200 || $status_code >= 300 || $body === '') {
            $error_prefix = $depth > 0 ? 'unreachable_child_sitemap:' : 'unreachable_sitemap:';
            $errors[] = $error_prefix . $status_code . ':' . substr(hash('sha256', $sitemap_url), 0, 12);
            return;
        }

        $lower = strtolower(substr($body, 0, 5000));
        $locs = $this->aysa_technical_sitemap_extract_loc_values($body);
        if (strpos($lower, '<sitemapindex') !== false) {
            foreach ($locs as $loc) {
                if (count($children) >= intval($bounds['max_child_sitemaps_inspected'])) {
                    break;
                }
                if ($target_url !== '' && !$this->aysa_technical_sitemap_same_host($target_url, $loc)) {
                    $errors[] = 'cross_host_child_sitemap_skipped';
                    continue;
                }
                $children[] = $loc;
                if ($depth < 2) {
                    $this->aysa_technical_parse_sitemap_inventory($loc, $target_url, $depth + 1, $seen, $urls, $children, $errors);
                }
                if (count($urls) >= intval($bounds['max_urls_parsed'])) {
                    break;
                }
            }
            return;
        }

        if (strpos($lower, '<urlset') !== false || $locs !== array()) {
            foreach ($locs as $loc) {
                if (count($urls) >= intval($bounds['max_urls_parsed'])) {
                    break;
                }
                if ($target_url !== '' && !$this->aysa_technical_sitemap_same_host($target_url, $loc)) {
                    $errors[] = 'cross_host_url_skipped';
                    continue;
                }
                $urls[$loc] = true;
            }
            return;
        }

        $error_prefix = $depth > 0 ? 'invalid_child_sitemap_xml:' : 'invalid_sitemap_xml:';
        $errors[] = $error_prefix . substr(hash('sha256', $sitemap_url), 0, 12);
    }

    private function aysa_technical_build_current_provider_sitemap_inventory($target_url, $primary_sitemap, array $providers, &$url_list_out = null) {
        $bounds = $this->aysa_technical_sitemap_inventory_bounds();
        $seen = array();
        $urls = array();
        $children = array();
        $errors = array();
        $primary_sitemap = esc_url_raw((string) $primary_sitemap);
        if ($primary_sitemap !== '') {
            $this->aysa_technical_parse_sitemap_inventory($primary_sitemap, (string) $target_url, 0, $seen, $urls, $children, $errors);
        }
        $url_list = array_keys($urls);
        sort($url_list, SORT_STRING);
        $url_list_out = $url_list;
        $sample = array_slice($url_list, 0, intval($bounds['max_url_sample']));

        $unique_children = array_values(array_unique($children));
        $all_unique_errors = array_values(array_unique($errors));
        $unique_errors = array_slice($all_unique_errors, 0, 20);
        $unreachable_child_count = 0;
        $invalid_child_count = 0;
        foreach ($all_unique_errors as $inventory_error) {
            if (strpos((string) $inventory_error, 'unreachable_child_sitemap:') === 0) {
                $unreachable_child_count++;
            } elseif (strpos((string) $inventory_error, 'invalid_child_sitemap_xml:') === 0) {
                $invalid_child_count++;
            }
        }
        $unhealthy_child_count = min(count($unique_children), $unreachable_child_count + $invalid_child_count);

        return array(
            'source' => 'bounded_live_provider_sitemap_parse',
            'detected_provider' => implode(', ', array_slice($providers, 0, 4)),
            'sitemap_index_url' => $primary_sitemap,
            'child_sitemap_urls' => array_slice($unique_children, 0, intval($bounds['max_child_sitemaps_inspected'])),
            'child_sitemap_count' => count($unique_children),
            'reachable_child_sitemap_count' => max(0, count($unique_children) - $unhealthy_child_count),
            'unreachable_child_sitemap_count' => $unreachable_child_count,
            'invalid_child_sitemap_count' => $invalid_child_count,
            'children_healthy' => count($unique_children) === 0 || $unhealthy_child_count === 0,
            'url_count' => count($url_list),
            'url_sample' => $sample,
            'url_list_hash' => hash('sha256', implode("\n", $url_list)),
            'xml_validity' => count($url_list) > 0 ? 'parsed_bounded' : 'insufficient_data',
            'errors' => $unique_errors,
            'bounded_limits' => $bounds,
            'active' => true,
            'writes_enabled' => false,
        );
    }

    private function aysa_technical_build_aysa_candidate_sitemap_inventory($target_url, &$url_list_out = null) {
        $bounds = $this->aysa_technical_sitemap_inventory_bounds();
        $target_url = trim((string) $target_url);
        if ($target_url === '') {
            $target_url = home_url('/');
        }

        $groups = $this->aysa_technical_collect_sitemap_groups();
        $urls = array();
        $children = array();
        foreach ($groups as $group) {
            if (!is_array($group)) {
                continue;
            }
            $entries = array_values((array) ($group['entries'] ?? array()));
            $chunks = array_chunk($entries, 1000);
            foreach ($chunks as $page => $chunk) {
                $children[] = array(
                    'key' => sanitize_key((string) ($group['key'] ?? 'sitemap')),
                    'label' => sanitize_text_field((string) ($group['label'] ?? 'Sitemap')),
                    'url' => untrailingslashit($target_url) . '/' . $this->aysa_technical_sitemap_child_slug((string) ($group['key'] ?? 'sitemap'), $page + 1),
                    'entries_count' => count($chunk),
                    'page' => $page + 1,
                );
                foreach ($chunk as $entry) {
                    $loc = esc_url_raw((string) ($entry['loc'] ?? ''));
                    if ($loc !== '') {
                        $urls[$loc] = true;
                    }
                }
            }
        }

        $url_list = array_keys($urls);
        sort($url_list, SORT_STRING);
        $url_list_out = $url_list;
        return array(
            'source' => 'in_memory_aysa_candidate_builder',
            'candidate_provider' => 'AYSA',
            'candidate_sitemap_index_url' => $this->aysa_technical_preferred_sitemap_url($target_url),
            'candidate_child_sitemaps' => array_slice($children, 0, intval($bounds['max_child_sitemaps_inspected'])),
            'candidate_child_sitemap_count' => count($children),
            'candidate_url_count' => count($url_list),
            'candidate_url_sample' => array_slice($url_list, 0, intval($bounds['max_url_sample'])),
            'candidate_url_list_hash' => hash('sha256', implode("\n", $url_list)),
            'candidate_xml_validity' => count($url_list) > 0 ? 'candidate_index_shape_valid' : 'insufficient_data',
            'candidate_robots_declaration' => 'Sitemap: ' . $this->aysa_technical_preferred_sitemap_url($target_url),
            'generated_in_memory' => true,
            'non_runtime_draft' => true,
            'active' => false,
            'writes_enabled' => false,
            'bounded_limits' => $bounds,
        );
    }

    private function aysa_technical_sitemap_url_category($url, $target_url = '') {
        $path = wp_parse_url((string) $url, PHP_URL_PATH);
        $query = wp_parse_url((string) $url, PHP_URL_QUERY);
        $path = '/' . ltrim(strtolower(is_string($path) ? $path : '/'), '/');
        $path = rtrim($path, '/') . '/';
        if ($path === '//') {
            $path = '/';
        }

        $post_id = url_to_postid((string) $url);
        if ($post_id > 0) {
            $type = get_post_type($post_id);
            return $type ? 'post_type:' . sanitize_key((string) $type) : 'post_type:unknown';
        }
        if (is_string($query) && $query !== '') {
            return 'query_parameter_url';
        }
        if (preg_match('#/page/[0-9]+/?$#', $path)) {
            return 'pagination';
        }
        if (strpos($path, '/feed/') !== false || substr($path, -6) === '/feed/') {
            return 'feed';
        }
        if (preg_match('#/(category|tag|product-category|product-tag|location|locations|vehicle|vehicles|car-category|car-types|car-capacity|car-steering)/#', $path)) {
            return 'taxonomy_or_archive';
        }
        if (preg_match('#^/[a-z]{2}/#', $path)) {
            return 'multilingual_path';
        }
        if (strpos($path, '/wp-content/uploads/') !== false) {
            return 'media_file';
        }
        if (strpos($path, '/author/') !== false) {
            return 'author_archive';
        }
        if (strpos($path, '/search/') !== false) {
            return 'search_url';
        }
        return 'unresolved_public_url';
    }

    private function aysa_technical_sitemap_category_counts(array $urls, $target_url = '') {
        $counts = array();
        foreach ($urls as $url) {
            $category = $this->aysa_technical_sitemap_url_category((string) $url, $target_url);
            if (!isset($counts[$category])) {
                $counts[$category] = 0;
            }
            $counts[$category]++;
        }
        arsort($counts);
        return $counts;
    }

    private function aysa_technical_sitemap_residual_reason($url, $side = 'missing_from_candidate', $target_url = '') {
        $url = esc_url_raw((string) $url);
        $side = sanitize_key((string) $side);
        $path = wp_parse_url($url, PHP_URL_PATH);
        $path = '/' . ltrim(strtolower(is_string($path) ? $path : '/'), '/');
        $path = rtrim($path, '/') . '/';
        if ($path === '//') {
            $path = '/';
        }

        foreach (array('/cart/', '/checkout/', '/my-account/', '/account/', '/search/') as $utility_path) {
            if (strpos($path, $utility_path) !== false) {
                return 'utility_url_excluded_by_aysa_policy';
            }
        }
        if (strpos($path, '/glossary/') !== false || strpos($path, '/glossario/') !== false) {
            return 'glossary_route_needs_provider_contract';
        }
        if (strpos($path, '/lista-masini/') !== false || strpos($path, '/car-rental/') !== false) {
            return 'custom_listing_rewrite_needs_entity_mapping';
        }

        $category = $this->aysa_technical_sitemap_url_category($url, $target_url);
        if ($category === 'multilingual_path') {
            return 'multilingual_slug_residual_needs_mapping';
        }
        if ($category === 'taxonomy_or_archive') {
            return 'taxonomy_archive_residual_needs_mapping';
        }
        if (strpos($category, 'post_type:') === 0) {
            if ($side === 'extra_in_candidate') {
                return 'candidate_page_provider_excludes_needs_review';
            }
            return 'provider_page_missing_from_candidate_needs_mapping';
        }
        if ($category === 'unresolved_public_url') {
            return 'unresolved_provider_url_needs_review';
        }
        return 'residual_diff_needs_manual_classification';
    }

    private function aysa_technical_sitemap_residual_reason_policy($reason) {
        $reason = sanitize_key((string) $reason);
        $policy = array(
            'utility_url_excluded_by_aysa_policy' => array(
                'severity' => 'non_blocking_policy_exclusion',
                'execution_status' => 'excluded_by_sitemap_policy',
                'next_step' => 'confirm_provider_should_not_keep_utility_url_in_sitemap',
            ),
            'candidate_page_provider_excludes_needs_review' => array(
                'severity' => 'review_needed',
                'execution_status' => 'candidate_only_review',
                'next_step' => 'confirm_candidate_page_should_remain_in_aysa_inventory',
            ),
            'glossary_route_needs_provider_contract' => array(
                'severity' => 'contract_needed',
                'execution_status' => 'provider_contract_needed',
                'next_step' => 'add_glossary_route_inventory_contract',
            ),
            'custom_listing_rewrite_needs_entity_mapping' => array(
                'severity' => 'contract_needed',
                'execution_status' => 'entity_mapping_needed',
                'next_step' => 'map_custom_listing_routes_to_public_entities',
            ),
            'multilingual_slug_residual_needs_mapping' => array(
                'severity' => 'contract_needed',
                'execution_status' => 'translated_slug_mapping_needed',
                'next_step' => 'extend_multilingual_slug_resolver',
            ),
            'taxonomy_archive_residual_needs_mapping' => array(
                'severity' => 'contract_needed',
                'execution_status' => 'taxonomy_archive_mapping_needed',
                'next_step' => 'map_taxonomy_archive_provider_rules',
            ),
            'provider_page_missing_from_candidate_needs_mapping' => array(
                'severity' => 'contract_needed',
                'execution_status' => 'provider_page_mapping_needed',
                'next_step' => 'map_provider_page_to_wordpress_entity_or_exclusion',
            ),
            'unresolved_provider_url_needs_review' => array(
                'severity' => 'manual_review_needed',
                'execution_status' => 'unresolved_provider_url',
                'next_step' => 'review_unresolved_provider_url',
            ),
        );
        return isset($policy[$reason]) ? $policy[$reason] : array(
            'severity' => 'manual_review_needed',
            'execution_status' => 'manual_classification_needed',
            'next_step' => 'classify_residual_url',
        );
    }

    private function aysa_technical_sitemap_residual_classification(array $missing, array $extra, $target_url = '') {
        $classify = function(array $urls, $side) use ($target_url) {
            $counts = array();
            $samples = array();
            $policy_by_reason = array();
            foreach ($urls as $url) {
                $reason = $this->aysa_technical_sitemap_residual_reason((string) $url, (string) $side, $target_url);
                if (!isset($counts[$reason])) {
                    $counts[$reason] = 0;
                }
                $counts[$reason]++;
                if (!isset($policy_by_reason[$reason])) {
                    $policy_by_reason[$reason] = $this->aysa_technical_sitemap_residual_reason_policy($reason);
                }
                if (!isset($samples[$reason])) {
                    $samples[$reason] = array();
                }
                if (count($samples[$reason]) < 5) {
                    $samples[$reason][] = esc_url_raw((string) $url);
                }
            }
            arsort($counts);
            return array(
                'counts' => $counts,
                'sample_by_reason' => $samples,
                'policy_by_reason' => $policy_by_reason,
            );
        };

        $missing_classification = $classify($missing, 'missing_from_candidate');
        $extra_classification = $classify($extra, 'extra_in_candidate');
        $all_reasons = array_values(array_unique(array_merge(
            array_keys((array) ($missing_classification['counts'] ?? array())),
            array_keys((array) ($extra_classification['counts'] ?? array()))
        )));
        sort($all_reasons, SORT_STRING);
        $blocking_reasons = array();
        $review_reasons = array();
        $non_blocking_reasons = array();
        $policy_by_reason = array();
        foreach ($all_reasons as $reason) {
            $policy = $this->aysa_technical_sitemap_residual_reason_policy($reason);
            $policy_by_reason[$reason] = $policy;
            $severity = (string) ($policy['severity'] ?? 'manual_review_needed');
            if ($severity === 'non_blocking_policy_exclusion') {
                $non_blocking_reasons[] = $reason;
            } elseif ($severity === 'review_needed') {
                $review_reasons[] = $reason;
            } else {
                $blocking_reasons[] = $reason;
            }
        }
        sort($blocking_reasons, SORT_STRING);
        sort($review_reasons, SORT_STRING);
        sort($non_blocking_reasons, SORT_STRING);
        $takeover_status = 'no_residual_diff';
        if ($blocking_reasons !== array()) {
            $takeover_status = 'blocked_by_residual_contracts';
        } elseif ($review_reasons !== array()) {
            $takeover_status = 'manual_review_required';
        } elseif ($non_blocking_reasons !== array()) {
            $takeover_status = 'policy_exclusions_only';
        }

        return array(
            'source' => 'bounded_residual_diff_classifier',
            'missing_from_candidate' => $missing_classification,
            'extra_in_candidate' => $extra_classification,
            'policy_by_reason' => $policy_by_reason,
            'all_reasons' => $all_reasons,
            'blocking_reasons' => $blocking_reasons,
            'review_reasons' => $review_reasons,
            'non_blocking_reasons' => $non_blocking_reasons,
            'blocking_count' => count($blocking_reasons),
            'review_count' => count($review_reasons),
            'non_blocking_count' => count($non_blocking_reasons),
            'takeover_status' => $takeover_status,
            'writes_enabled' => false,
        );
    }

    private function aysa_technical_build_sitemap_parity_diff(array $current_urls, array $candidate_urls, $target_url = '') {
        $bounds = $this->aysa_technical_sitemap_inventory_bounds();
        $current_map = array_fill_keys($current_urls, true);
        $candidate_map = array_fill_keys($candidate_urls, true);
        $missing = array_values(array_diff($current_urls, $candidate_urls));
        $extra = array_values(array_diff($candidate_urls, $current_urls));
        sort($missing, SORT_STRING);
        sort($extra, SORT_STRING);

        return array(
            'source' => 'bounded_in_memory_url_set_diff',
            'missing_from_candidate_count' => count($missing),
            'extra_in_candidate_count' => count($extra),
            'overlap_count' => count(array_intersect_key($current_map, $candidate_map)),
            'missing_from_candidate_sample' => array_slice($missing, 0, intval($bounds['max_url_sample'])),
            'extra_in_candidate_sample' => array_slice($extra, 0, intval($bounds['max_url_sample'])),
            'missing_from_candidate_categories' => $this->aysa_technical_sitemap_category_counts($missing, $target_url),
            'extra_in_candidate_categories' => $this->aysa_technical_sitemap_category_counts($extra, $target_url),
            'residual_classification' => $this->aysa_technical_sitemap_residual_classification($missing, $extra, $target_url),
            'bounded_limits' => array(
                'max_missing_sample' => intval($bounds['max_url_sample']),
                'max_extra_sample' => intval($bounds['max_url_sample']),
                'full_url_lists_exposed' => false,
            ),
            'writes_enabled' => false,
        );
    }

    private function aysa_technical_sitemap_authority_parity_summary(array $current_provider_inventory, array $aysa_candidate_inventory, array $sitemap_parity_diff) {
        $current_count = intval($current_provider_inventory['url_count'] ?? 0);
        $candidate_count = intval($aysa_candidate_inventory['candidate_url_count'] ?? 0);
        $current_hash = (string) ($current_provider_inventory['url_list_hash'] ?? '');
        $candidate_hash = (string) ($aysa_candidate_inventory['candidate_url_list_hash'] ?? '');
        $missing_count = isset($sitemap_parity_diff['missing_from_candidate_count'])
            ? intval($sitemap_parity_diff['missing_from_candidate_count'])
            : max(0, $current_count - $candidate_count);
        $extra_count = isset($sitemap_parity_diff['extra_in_candidate_count'])
            ? intval($sitemap_parity_diff['extra_in_candidate_count'])
            : max(0, $candidate_count - $current_count);
        $overlap_count = isset($sitemap_parity_diff['overlap_count'])
            ? intval($sitemap_parity_diff['overlap_count'])
            : min($current_count, $candidate_count);

        $status = 'insufficient_data';
        $risk = 'high';
        $reasons = array();
        if ($current_count <= 0 || $candidate_count <= 0) {
            $reasons[] = 'bounded_url_inventory_missing';
        } elseif ($current_hash !== '' && $candidate_hash !== '' && hash_equals($current_hash, $candidate_hash)) {
            $status = 'parity_pass';
            $risk = 'medium';
        } elseif ($missing_count === 0 && $extra_count <= max(3, intval(floor($current_count * 0.02)))) {
            $status = 'parity_pass';
            $risk = 'medium';
        } elseif ($missing_count <= max(5, intval(floor($current_count * 0.05)))) {
            $status = 'parity_warning';
            $risk = 'high';
            $reasons[] = 'candidate_diff_requires_review';
        } else {
            $status = 'parity_fail';
            $risk = 'blocked';
            $reasons[] = 'candidate_missing_too_many_current_urls';
        }

        return array(
            'parity_status' => $status,
            'risk_level' => $risk,
            'current_url_count' => $current_count,
            'candidate_url_count' => $candidate_count,
            'overlap_count' => $overlap_count,
            'missing_from_candidate_count' => $missing_count,
            'extra_in_candidate_count' => $extra_count,
            'reasons' => $reasons,
        );
    }

    private function aysa_technical_sitemap_authority_parity_decision(array $parity, array $sitemap_parity_diff) {
        $status = sanitize_key((string) ($parity['parity_status'] ?? 'insufficient_data'));
        $residual = is_array($sitemap_parity_diff['residual_classification'] ?? null) ? $sitemap_parity_diff['residual_classification'] : array();
        $blocking = is_array($residual['blocking_reasons'] ?? null) ? array_values($residual['blocking_reasons']) : array();
        $review = is_array($residual['review_reasons'] ?? null) ? array_values($residual['review_reasons']) : array();
        $policy = is_array($residual['non_blocking_reasons'] ?? null) ? array_values($residual['non_blocking_reasons']) : array();
        $decision = 'insufficient_data';
        $next_step = 'build_provider_and_candidate_inventory';
        $staging_allowed = false;

        if ($status === 'parity_pass') {
            $decision = 'parity_pass';
            $next_step = 'continue_to_backup_restore';
            $staging_allowed = true;
        } elseif ($status === 'parity_warning') {
            if ($blocking !== array()) {
                $decision = 'residual_contracts_blocking';
                $next_step = 'resolve_residual_contracts_before_staging';
            } elseif ($review !== array()) {
                $decision = 'residual_manual_review_required';
                $next_step = 'complete_residual_manual_review';
            } elseif ($policy !== array()) {
                $decision = 'residual_policy_exclusions_need_ack';
                $next_step = 'acknowledge_policy_exclusions_before_staging';
            } else {
                $decision = 'candidate_diff_requires_review';
                $next_step = 'review_bounded_candidate_diff';
            }
        } elseif ($status === 'parity_fail') {
            $decision = 'parity_fail';
            $next_step = 'improve_candidate_inventory_or_provider_mapping';
        }

        return array(
            'operation' => 'technical_sitemap_authority_parity_decision',
            'status' => $decision,
            'parity_status' => $status,
            'staging_allowed' => $staging_allowed,
            'apply_allowed' => false,
            'writes_enabled' => false,
            'billing_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'blocking_reasons' => $blocking,
            'review_reasons' => $review,
            'non_blocking_reasons' => $policy,
            'next_step' => $next_step,
        );
    }

    private function aysa_technical_sitemap_authority_backup_history_option_key($project_id) {
        return 'aysa_agent8v3_sitemap_authority_backup_history_' . intval($project_id);
    }

    private function aysa_technical_sitemap_authority_backup_history($project_id) {
        $rows = get_option($this->aysa_technical_sitemap_authority_backup_history_option_key($project_id), array());
        return is_array($rows) ? array_values($rows) : array();
    }

    private function aysa_technical_sitemap_authority_save_backup_history($project_id, array $rows) {
        update_option($this->aysa_technical_sitemap_authority_backup_history_option_key($project_id), array_slice(array_values($rows), -100), false);
    }

    private function aysa_technical_sitemap_authority_backup_fingerprint($target_url, array $audit) {
        $current_provider_inventory = is_array($audit['current_provider_inventory'] ?? null) ? $audit['current_provider_inventory'] : array();
        $aysa_candidate_inventory = is_array($audit['aysa_candidate_inventory'] ?? null) ? $audit['aysa_candidate_inventory'] : array();
        $robots = is_array($audit['robots'] ?? null) ? $audit['robots'] : array();
        $robots_content = (string) ($robots['content'] ?? '');
        return hash('sha256', implode('|', array(
            esc_url_raw((string) $target_url),
            (string) ($current_provider_inventory['url_list_hash'] ?? ''),
            (string) ($aysa_candidate_inventory['candidate_url_list_hash'] ?? ''),
            $robots_content !== '' ? hash('sha256', $robots_content) : '',
        )));
    }

    private function aysa_technical_sitemap_authority_latest_matching_backup($project_id, $target_url, array $audit) {
        $fingerprint = $this->aysa_technical_sitemap_authority_backup_fingerprint($target_url, $audit);
        $history = $this->aysa_technical_sitemap_authority_backup_history($project_id);
        for ($i = count($history) - 1; $i >= 0; $i--) {
            $row = is_array($history[$i] ?? null) ? $history[$i] : array();
            if (hash_equals($fingerprint, (string) ($row['backup_fingerprint'] ?? ''))) {
                return $row;
            }
        }
        return array();
    }

    private function aysa_technical_sitemap_authority_backup_by_id($project_id, $backup_id) {
        $backup_id = sanitize_text_field((string) $backup_id);
        if ($backup_id === '') {
            return array();
        }
        foreach ($this->aysa_technical_sitemap_authority_backup_history($project_id) as $row) {
            $row = is_array($row) ? $row : array();
            if (hash_equals($backup_id, (string) ($row['backup_id'] ?? ''))) {
                return $row;
            }
        }
        return array();
    }

    private function aysa_technical_sitemap_authority_create_backup_snapshot($project_id, $target_url, array $audit) {
        $project_id = intval($project_id);
        $target_url = esc_url_raw((string) $target_url);
        if ($project_id <= 0 || $target_url === '') {
            return array(
                'status' => 'backup_context_missing',
                'active' => false,
                'writes_enabled' => false,
            );
        }

        $fingerprint = $this->aysa_technical_sitemap_authority_backup_fingerprint($target_url, $audit);
        $existing = $this->aysa_technical_sitemap_authority_latest_matching_backup($project_id, $target_url, $audit);
        if ($existing !== array()) {
            $existing['status'] = 'backup_already_exists';
            $existing['active'] = true;
            if (!empty($existing['robots_before_content'])) {
                $existing['restore_available'] = true;
                $existing['restore_status'] = 'restore_available';
            }
            $existing['writes_enabled'] = false;
            return $existing;
        }

        $current_provider_inventory = is_array($audit['current_provider_inventory'] ?? null) ? $audit['current_provider_inventory'] : array();
        $aysa_candidate_inventory = is_array($audit['aysa_candidate_inventory'] ?? null) ? $audit['aysa_candidate_inventory'] : array();
        $sitemap_parity_diff = is_array($audit['sitemap_parity_diff'] ?? null) ? $audit['sitemap_parity_diff'] : array();
        $robots = is_array($audit['robots'] ?? null) ? $audit['robots'] : array();
        $robots_content = (string) ($robots['content'] ?? '');
        $providers = is_array($audit['providers'] ?? null) ? array_values($audit['providers']) : array();
        $backup_id = 'sitemap_authority_backup_' . substr(hash('sha256', $fingerprint . '|' . current_time('mysql') . '|' . wp_salt('auth')), 0, 20);

        $row = array(
            'backup_id' => $backup_id,
            'backup_fingerprint' => $fingerprint,
            'project_id' => $project_id,
            'website_id' => intval(get_option('aysa_website_id', 0)),
            'target_url' => $target_url,
            'created_at' => current_time('mysql'),
            'created_by_agent' => 'agent8v3_sitemap_authority_dry_run_bridge',
            'current_provider' => sanitize_text_field((string) ($providers[0] ?? ($current_provider_inventory['detected_provider'] ?? 'unknown'))),
            'current_sitemap_index_url' => esc_url_raw((string) ($current_provider_inventory['sitemap_index_url'] ?? ($audit['primary_sitemap'] ?? ''))),
            'future_sitemap_index_url' => esc_url_raw((string) ($aysa_candidate_inventory['candidate_sitemap_index_url'] ?? $this->aysa_technical_preferred_sitemap_url($target_url))),
            'robots_source' => sanitize_text_field((string) ($robots['source'] ?? 'unknown')),
            'robots_exists' => !empty($robots['exists']),
            'robots_writable' => !empty($robots['writable']),
            'robots_before_hash' => $robots_content !== '' ? hash('sha256', $robots_content) : '',
            'robots_before_content' => $robots_content,
            'robots_before_size' => strlen($robots_content),
            'current_provider_inventory_hash' => sanitize_text_field((string) ($current_provider_inventory['url_list_hash'] ?? '')),
            'current_provider_url_count' => intval($current_provider_inventory['url_count'] ?? 0),
            'candidate_inventory_hash' => sanitize_text_field((string) ($aysa_candidate_inventory['candidate_url_list_hash'] ?? '')),
            'candidate_url_count' => intval($aysa_candidate_inventory['candidate_url_count'] ?? 0),
            'parity_missing_count' => intval($sitemap_parity_diff['missing_from_candidate_count'] ?? 0),
            'parity_extra_count' => intval($sitemap_parity_diff['extra_in_candidate_count'] ?? 0),
            'child_sitemap_urls_sample' => is_array($current_provider_inventory['child_sitemap_urls'] ?? null) ? array_slice($current_provider_inventory['child_sitemap_urls'], 0, 20) : array(),
            'candidate_child_sitemaps_sample' => is_array($aysa_candidate_inventory['candidate_child_sitemaps'] ?? null) ? array_slice($aysa_candidate_inventory['candidate_child_sitemaps'], 0, 20) : array(),
            'residual_blocking_reasons' => is_array($sitemap_parity_diff['residual_classification']['blocking_reasons'] ?? null) ? array_slice($sitemap_parity_diff['residual_classification']['blocking_reasons'], 0, 20) : array(),
            'restore_available' => $robots_content !== '',
            'restore_status' => $robots_content !== '' ? 'restore_available' : 'restore_requires_backup_content',
            'verified' => false,
            'billing_enabled' => false,
            'writes_enabled' => false,
            'active' => true,
            'status' => 'backup_created',
        );

        $history = $this->aysa_technical_sitemap_authority_backup_history($project_id);
        $history[] = $row;
        $this->aysa_technical_sitemap_authority_save_backup_history($project_id, $history);
        return $row;
    }

    private function aysa_technical_sitemap_authority_restore_preview($project_id, array $backup, array $audit) {
        $project_id = intval($project_id);
        $backup_id = sanitize_text_field((string) ($backup['backup_id'] ?? ''));
        if ($project_id <= 0 || $backup_id === '') {
            return array(
                'operation' => 'technical_sitemap_authority_restore_preview',
                'status' => 'backup_missing',
                'dry_run' => true,
                'restore_execution_available' => false,
                'writes_enabled' => false,
                'billing_enabled' => false,
                'billing_status' => 'not_billable_restore',
            );
        }

        $current_provider_inventory = is_array($audit['current_provider_inventory'] ?? null) ? $audit['current_provider_inventory'] : array();
        $aysa_candidate_inventory = is_array($audit['aysa_candidate_inventory'] ?? null) ? $audit['aysa_candidate_inventory'] : array();
        $robots = is_array($audit['robots'] ?? null) ? $audit['robots'] : array();
        $robots_content = (string) ($robots['content'] ?? '');
        $current_robots_hash = $robots_content !== '' ? hash('sha256', $robots_content) : '';
        $provider_hash = sanitize_text_field((string) ($current_provider_inventory['url_list_hash'] ?? ''));
        $candidate_hash = sanitize_text_field((string) ($aysa_candidate_inventory['candidate_url_list_hash'] ?? ''));
        $checks = array(
            'robots_hash_matches_backup' => $current_robots_hash !== '' && hash_equals((string) ($backup['robots_before_hash'] ?? ''), $current_robots_hash),
            'provider_inventory_matches_backup' => $provider_hash !== '' && hash_equals((string) ($backup['current_provider_inventory_hash'] ?? ''), $provider_hash),
            'candidate_inventory_matches_backup' => $candidate_hash !== '' && hash_equals((string) ($backup['candidate_inventory_hash'] ?? ''), $candidate_hash),
        );
        $warning_count = 0;
        foreach ($checks as $check) {
            if (!$check) {
                $warning_count++;
            }
        }

        return array(
            'operation' => 'technical_sitemap_authority_restore_preview',
            'status' => $warning_count === 0 ? 'restore_preview_ready' : 'restore_preview_requires_review',
            'backup_id' => $backup_id,
            'project_id' => $project_id,
            'generated_at' => current_time('mysql'),
            'dry_run' => true,
            'restore_preview_available' => true,
            'restore_execution_available' => false,
            'restore_available' => false,
            'restore_status' => 'restore_execution_not_implemented',
            'writes_enabled' => false,
            'billing_enabled' => false,
            'billing_status' => 'not_billable_restore',
            'provider_changed' => false,
            'robots_changed' => false,
            'sitemap_written' => false,
            'gsc_submitted' => false,
            'checks' => $checks,
            'warning_count' => $warning_count,
            'backup_reference' => array(
                'current_provider' => sanitize_text_field((string) ($backup['current_provider'] ?? 'unknown')),
                'current_sitemap_index_url' => esc_url_raw((string) ($backup['current_sitemap_index_url'] ?? '')),
                'future_sitemap_index_url' => esc_url_raw((string) ($backup['future_sitemap_index_url'] ?? '')),
                'robots_before_hash' => sanitize_text_field((string) ($backup['robots_before_hash'] ?? '')),
                'current_provider_inventory_hash' => sanitize_text_field((string) ($backup['current_provider_inventory_hash'] ?? '')),
                'candidate_inventory_hash' => sanitize_text_field((string) ($backup['candidate_inventory_hash'] ?? '')),
            ),
            'current_readback' => array(
                'robots_hash' => $current_robots_hash,
                'current_provider_inventory_hash' => $provider_hash,
                'candidate_inventory_hash' => $candidate_hash,
                'current_provider_url_count' => intval($current_provider_inventory['url_count'] ?? 0),
                'candidate_url_count' => intval($aysa_candidate_inventory['candidate_url_count'] ?? 0),
            ),
            'future_restore_steps' => array(
                'disable_aysa_sitemap_authority',
                'restore_previous_robots_sitemap_declaration_or_virtual_output',
                'restore_provider_owned_sitemap_index_authority',
                'readback_verify_robots_and_sitemap_outputs',
            ),
            'blockers' => array(
                'restore_execution_not_implemented',
                'active_apply_bridge_missing',
                'verified_only_billing_missing',
            ),
        );
    }

    private function aysa_technical_sitemap_authority_restore_execution_contract(array $backup, array $restore_preview = array(), array $readback = array()) {
        $backup_id = sanitize_text_field((string) ($backup['backup_id'] ?? ''));
        $backup_active = !empty($backup['active']) && $backup_id !== '';
        $preview_ready = !empty($restore_preview['restore_preview_available']);
        $readback_available = !empty($readback['readback_available']);
        $preconditions = array(
            'backup_active' => $backup_active,
            'restore_preview_available' => $preview_ready,
            'post_takeover_readback_available' => $readback_available,
            'restore_verified_non_billable_policy' => true,
            'production_apply_not_allowed' => false,
        );
        $missing = array();
        foreach ($preconditions as $key => $passed) {
            if (!$passed) {
                $missing[] = sanitize_key((string) $key);
            }
        }

        return array(
            'operation' => 'technical_sitemap_authority_restore_execution_contract',
            'status' => 'restore_execution_contract_disabled_until_staging',
            'backup_id' => $backup_id,
            'dry_run' => true,
            'active' => false,
            'selectable' => false,
            'restore_execution_available' => false,
            'restore_available' => false,
            'restore_billing_allowed' => false,
            'billing_enabled' => false,
            'billing_status' => 'not_billable_restore',
            'writes_enabled' => false,
            'provider_changed' => false,
            'robots_changed' => false,
            'sitemap_written' => false,
            'gsc_submitted' => false,
            'idempotency_key' => $backup_id !== '' ? 'sitemap_authority_restore:' . $backup_id : '',
            'preconditions' => $preconditions,
            'missing_preconditions' => $missing,
            'required_confirmation_fields' => array(
                'backup_id',
                'target_url',
                'restore_scope',
                'restore_reason',
                'approved_by_user',
            ),
            'restore_steps' => array(
                'disable_aysa_sitemap_authority_runtime',
                'restore_previous_robots_sitemap_declaration_or_virtual_output',
                'restore_provider_owned_sitemap_index_authority',
                'run_post_restore_readback',
                'record_non_billable_restore_history',
            ),
            'blockers' => array_values(array_unique(array_merge($missing, array(
                'restore_execution_not_implemented',
                'staging_restore_not_verified',
                'production_apply_not_allowed',
            )))),
        );
    }

    private function aysa_technical_sitemap_authority_post_takeover_readback_contract(array $backup, array $restore_preview = array(), array $audit = array()) {
        $current_provider_inventory = is_array($audit['current_provider_inventory'] ?? null) ? $audit['current_provider_inventory'] : array();
        $aysa_candidate_inventory = is_array($audit['aysa_candidate_inventory'] ?? null) ? $audit['aysa_candidate_inventory'] : array();
        $robots = is_array($audit['robots'] ?? null) ? $audit['robots'] : array();
        $robots_content = (string) ($robots['content'] ?? '');
        $future_sitemap = esc_url_raw((string) ($backup['future_sitemap_index_url'] ?? ($aysa_candidate_inventory['candidate_sitemap_index_url'] ?? '')));

        return array(
            'operation' => 'technical_sitemap_authority_post_takeover_readback_contract',
            'status' => 'readback_disabled_until_active_takeover',
            'backup_id' => sanitize_text_field((string) ($backup['backup_id'] ?? '')),
            'dry_run' => true,
            'active' => false,
            'selectable' => false,
            'readback_available' => false,
            'readback_execution_available' => false,
            'verified' => false,
            'verified_change_count' => 0,
            'writes_enabled' => false,
            'billing_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'provider_changed' => false,
            'robots_changed' => false,
            'sitemap_written' => false,
            'gsc_submitted' => false,
            'expected_after_apply' => array(
                'future_provider' => 'AYSA',
                'future_sitemap_index_url' => $future_sitemap,
                'candidate_inventory_hash' => sanitize_text_field((string) ($backup['candidate_inventory_hash'] ?? ($aysa_candidate_inventory['candidate_url_list_hash'] ?? ''))),
                'candidate_url_count' => intval($backup['candidate_url_count'] ?? ($aysa_candidate_inventory['candidate_url_count'] ?? 0)),
                'current_provider_inventory_hash' => sanitize_text_field((string) ($backup['current_provider_inventory_hash'] ?? ($current_provider_inventory['url_list_hash'] ?? ''))),
                'current_provider_url_count' => intval($backup['current_provider_url_count'] ?? ($current_provider_inventory['url_count'] ?? 0)),
                'robots_before_hash' => sanitize_text_field((string) ($backup['robots_before_hash'] ?? ($robots_content !== '' ? hash('sha256', $robots_content) : ''))),
                'robots_future_declaration' => $future_sitemap !== '' ? 'Sitemap: ' . $future_sitemap : '',
            ),
            'required_checks' => array(
                'robots_contains_aysa_sitemap_declaration',
                'aysa_sitemap_index_http_200',
                'aysa_sitemap_index_parseable_xml',
                'aysa_sitemap_inventory_hash_matches_candidate',
                'legacy_provider_output_preserved_or_known_redirect',
                'restore_preview_still_available',
                'no_unapproved_gsc_submit',
            ),
            'current_status' => array(
                'restore_preview_available' => !empty($restore_preview['restore_preview_available']),
                'restore_execution_available' => !empty($restore_preview['restore_execution_available']),
                'provider_inventory_available' => intval($current_provider_inventory['url_count'] ?? 0) > 0,
                'candidate_inventory_available' => intval($aysa_candidate_inventory['candidate_url_count'] ?? 0) > 0,
            ),
            'blockers' => array(
                'active_takeover_not_executed',
                'readback_execution_not_implemented',
                'restore_execution_not_implemented',
                'billing_gate_off',
                'staging_takeover_not_verified',
                'production_approval_missing',
            ),
        );
    }

    private function aysa_technical_sitemap_authority_evaluate_post_takeover_readback($project_id, $target_url, array $backup, array $restore_preview, array $audit, array $contract, $active_takeover_executed = false) {
        $project_id = intval($project_id);
        $target_url = esc_url_raw((string) $target_url);
        $expected = is_array($contract['expected_after_apply'] ?? null) ? $contract['expected_after_apply'] : array();
        $future_sitemap = esc_url_raw((string) ($expected['future_sitemap_index_url'] ?? ''));
        $candidate_hash = sanitize_text_field((string) ($expected['candidate_inventory_hash'] ?? ''));
        $robots = is_array($audit['robots'] ?? null) ? $audit['robots'] : array();
        $robots_content = (string) ($robots['content'] ?? '');
        $robots_normalized = strtolower($robots_content);
        $future_declaration = $future_sitemap !== '' ? strtolower('Sitemap: ' . $future_sitemap) : '';

        $seen = array();
        $live_urls = array();
        $live_children = array();
        $live_errors = array();
        if ($future_sitemap !== '') {
            $this->aysa_technical_parse_sitemap_inventory($future_sitemap, $target_url, 0, $seen, $live_urls, $live_children, $live_errors);
        }
        $live_url_list = array_keys($live_urls);
        sort($live_url_list, SORT_STRING);
        $live_hash = hash('sha256', implode("\n", $live_url_list));
        $live_count = count($live_url_list);

        $sitemaps = is_array($audit['sitemaps'] ?? null) ? $audit['sitemaps'] : array();
        $legacy_ok = true;
        foreach ($sitemaps as $row) {
            $row = is_array($row) ? $row : array();
            if ((string) ($row['provider'] ?? '') === 'aysa') {
                continue;
            }
            $type = (string) ($row['type'] ?? '');
            $location = untrailingslashit((string) ($row['location'] ?? ''));
            if (!empty($row['reachable']) && $type !== 'redirect') {
                $legacy_ok = false;
                break;
            }
            if ($type === 'redirect' && $future_sitemap !== '' && $location !== '' && $location !== untrailingslashit($future_sitemap)) {
                $legacy_ok = false;
                break;
            }
        }

        $checks = array(
            'robots_contains_aysa_sitemap_declaration' => $future_declaration !== '' && strpos($robots_normalized, $future_declaration) !== false,
            'aysa_sitemap_index_http_200' => $future_sitemap !== '' && $live_count > 0 && !in_array('unreachable_sitemap:0', $live_errors, true),
            'aysa_sitemap_index_parseable_xml' => $future_sitemap !== '' && $live_count > 0 && !in_array('invalid_sitemap_xml', $live_errors, true),
            'aysa_sitemap_inventory_hash_matches_candidate' => $candidate_hash !== '' && $live_hash !== '' && hash_equals($candidate_hash, $live_hash),
            'legacy_provider_output_preserved_or_known_redirect' => $legacy_ok,
            'restore_preview_still_available' => !empty($restore_preview['restore_preview_available']),
            'no_unapproved_gsc_submit' => true,
        );
        $failed = array();
        foreach ($checks as $key => $passed) {
            if (!$passed) {
                $failed[] = $key;
            }
        }

        $active_takeover_verified = !empty($active_takeover_executed);
        $verified = $active_takeover_verified && $failed === array();
        $verified_change_count = $verified ? 0 : 0;
        if ($verified && !empty($checks['robots_contains_aysa_sitemap_declaration'])) {
            $verified_change_count++;
        }
        if ($verified && !empty($checks['aysa_sitemap_index_http_200']) && !empty($checks['aysa_sitemap_index_parseable_xml'])) {
            $verified_change_count++;
        }
        return array(
            'operation' => 'technical_sitemap_authority_post_takeover_readback',
            'status' => $verified ? 'verified' : ($active_takeover_verified ? 'completed_with_warnings' : 'not_verified_active_takeover_missing'),
            'project_id' => $project_id,
            'target_url' => $target_url,
            'backup_id' => sanitize_text_field((string) ($backup['backup_id'] ?? '')),
            'generated_at' => current_time('mysql'),
            'dry_run' => !$active_takeover_verified,
            'active' => $active_takeover_verified,
            'selectable' => false,
            'readback_available' => true,
            'readback_execution_available' => true,
            'verified' => $verified,
            'verified_change_count' => $verified_change_count,
            'writes_enabled' => false,
            'billing_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'provider_changed' => $active_takeover_verified,
            'robots_changed' => $active_takeover_verified && !empty($checks['robots_contains_aysa_sitemap_declaration']),
            'sitemap_written' => $active_takeover_verified && !empty($checks['aysa_sitemap_index_http_200']),
            'gsc_submitted' => false,
            'expected_after_apply' => $expected,
            'checks' => $checks,
            'failed_checks' => $failed,
            'live_readback' => array(
                'future_sitemap_index_url' => $future_sitemap,
                'live_url_count' => $live_count,
                'live_child_sitemap_count' => count(array_unique($live_children)),
                'live_url_list_hash' => $live_hash,
                'live_errors' => array_values(array_unique(array_slice($live_errors, 0, 20))),
                'robots_current_hash' => $robots_content !== '' ? hash('sha256', $robots_content) : '',
            ),
            'blockers' => array_values(array_unique(array_merge(array_filter(array(
                $active_takeover_verified ? '' : 'active_takeover_not_executed',
                'billing_gate_off',
            )), $failed))),
        );
    }

    private function aysa_technical_sitemap_authority_post_takeover_readback_shell($project_id, $payload) {
        $project_id = intval($project_id);
        $payload = is_array($payload) ? $payload : array();
        $target_url = isset($payload['target_url']) ? esc_url_raw((string) $payload['target_url']) : '';
        if ($target_url === '') {
            $target_url = $this->aysa_technical_resolve_target_url();
        }
        $audit = $this->aysa_technical_build_sitemap_robots_audit($target_url);
        $backup_id = sanitize_text_field((string) ($payload['backup_id'] ?? ''));
        $backup = $backup_id !== ''
            ? $this->aysa_technical_sitemap_authority_backup_by_id($project_id, $backup_id)
            : $this->aysa_technical_sitemap_authority_latest_matching_backup($project_id, $target_url, $audit);
        $restore_preview = $this->aysa_technical_sitemap_authority_restore_preview($project_id, $backup, $audit);
        $contract = $this->aysa_technical_sitemap_authority_post_takeover_readback_contract($backup, $restore_preview, $audit);
        $readback = $this->aysa_technical_sitemap_authority_evaluate_post_takeover_readback($project_id, $target_url, $backup, $restore_preview, $audit, $contract);
        $billing_contract = $this->aysa_technical_sitemap_authority_billing_contract($backup, $restore_preview, $readback);
        $readback['billing_contract'] = $billing_contract;
        return $readback;
    }

    private function aysa_technical_sitemap_authority_takeover_history_contract(array $backup, array $restore_preview = array(), array $readback = array()) {
        $backup_id = sanitize_text_field((string) ($backup['backup_id'] ?? ''));
        return array(
            'operation' => 'technical_sitemap_authority_takeover_history_contract',
            'status' => 'history_disabled_until_active_takeover',
            'backup_id' => $backup_id,
            'dry_run' => true,
            'active' => false,
            'selectable' => false,
            'history_execution_available' => false,
            'history_row_available' => false,
            'restore_reference_available' => false,
            'writes_enabled' => false,
            'billing_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'provider_changed' => false,
            'robots_changed' => false,
            'sitemap_written' => false,
            'gsc_submitted' => false,
            'idempotency_key' => $backup_id !== '' ? 'sitemap_authority_takeover:' . $backup_id : '',
            'required_history_fields' => array(
                'history_id',
                'backup_id',
                'project_id',
                'website_id',
                'target_url',
                'approved_by_user',
                'approved_at',
                'current_provider_before',
                'future_provider_after',
                'robots_before_hash',
                'robots_after_hash',
                'current_provider_inventory_hash',
                'candidate_inventory_hash',
                'apply_result',
                'readback_result',
                'verified_change_count',
                'billing_idempotency_key',
                'restore_status',
            ),
            'requires' => array(
                'backup_active' => !empty($backup['active']) && $backup_id !== '',
                'restore_preview_available' => !empty($restore_preview['restore_preview_available']),
                'post_takeover_readback_available' => !empty($readback['readback_available']),
                'post_takeover_verified' => !empty($readback['verified']),
            ),
            'blockers' => array(
                'active_takeover_not_executed',
                'history_execution_not_implemented',
                'restore_execution_not_implemented',
                'readback_not_verified',
                'billing_gate_off',
            ),
        );
    }

    private function aysa_technical_sitemap_authority_staging_preflight_contract($staging_target_url, $staging_project_id) {
        $staging_target_url = esc_url_raw((string) $staging_target_url);
        $staging_project_id = intval($staging_project_id);
        $current_home = esc_url_raw((string) home_url('/'));
        $current_host = strtolower((string) wp_parse_url($current_home, PHP_URL_HOST));
        $staging_host = strtolower((string) wp_parse_url($staging_target_url, PHP_URL_HOST));
        $configured = $staging_target_url !== '' && $staging_project_id > 0;
        $same_host = $configured && $current_host !== '' && $staging_host !== '' && $current_host === $staging_host;
        $blockers = array_values(array_filter(array(
            $configured ? '' : 'staging_target_missing',
            $same_host ? 'staging_target_matches_production_host' : '',
            'staging_apply_execution_not_implemented',
        )));

        return array(
            'operation' => 'technical_sitemap_authority_staging_preflight',
            'status' => empty($blockers) ? 'staging_target_configured' : 'staging_preflight_blocked',
            'dry_run' => true,
            'active' => false,
            'selectable' => false,
            'configured' => $configured,
            'same_host_as_production' => $same_host,
            'live_http_checked' => false,
            'staging_execution_available' => false,
            'writes_enabled' => false,
            'billing_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'provider_changed' => false,
            'robots_changed' => false,
            'sitemap_written' => false,
            'gsc_submitted' => false,
            'current_home_url' => $current_home,
            'current_host' => sanitize_text_field($current_host),
            'staging_target_url' => $staging_target_url,
            'staging_host' => sanitize_text_field($staging_host),
            'staging_project_id' => $staging_project_id,
            'required_checks_before_staging_apply' => array(
                'target_is_not_production_host',
                'staging_wordpress_reachable',
                'aysa_plugin_active_on_staging',
                'projection_secret_present_on_staging',
                'provider_inventory_available_on_staging',
                'candidate_inventory_parity_pass_on_staging',
                'backup_restore_contract_ready_on_staging',
                'billing_dry_run_reconciles_without_charge',
            ),
            'blockers' => $blockers,
        );
    }

    private function aysa_technical_sitemap_authority_staging_evidence_model(array $staging_preflight, array $backup, array $restore_preview = array(), array $readback = array(), array $history_contract = array(), array $evidence_override = array(), array $restore_execution_contract = array(), array $billing_dry_run = array()) {
        $stored = $evidence_override !== array()
            ? $evidence_override
            : get_option('aysa_sitemap_authority_staging_evidence', array());
        $stored = is_array($stored) ? $stored : array();
        $verified_at = sanitize_text_field((string) ($stored['verified_at'] ?? ''));
        $evidence_source = $evidence_override !== array() ? 'payload_dry_run' : 'stored_evidence';
        $items = array(
            'target_is_not_production_host' => !empty($staging_preflight['configured']) && empty($staging_preflight['same_host_as_production']),
            'staging_wordpress_reachable' => !empty($stored['staging_wordpress_reachable']),
            'aysa_plugin_active_on_staging' => !empty($stored['aysa_plugin_active_on_staging']),
            'projection_secret_present_on_staging' => !empty($stored['projection_secret_present_on_staging']),
            'provider_inventory_available_on_staging' => !empty($stored['provider_inventory_available_on_staging']),
            'candidate_inventory_parity_pass_on_staging' => !empty($stored['candidate_inventory_parity_pass_on_staging']),
            'backup_snapshot_saved' => !empty($backup['active']) && (string) ($backup['backup_id'] ?? '') !== '',
            'restore_preview_available' => !empty($restore_preview['restore_preview_available']),
            'restore_execution_contract_available' => !empty($restore_execution_contract) && (string) ($restore_execution_contract['operation'] ?? '') === 'technical_sitemap_authority_restore_execution_contract',
            'post_takeover_readback_shell_available' => !empty($readback['readback_available']),
            'history_contract_available' => !empty($history_contract) && (string) ($history_contract['status'] ?? '') !== 'history_contract_missing',
            'billing_dry_run_reconciliation_available' => !empty($billing_dry_run) && (string) ($billing_dry_run['operation'] ?? '') === 'technical_sitemap_authority_billing_dry_run_reconciliation',
            'restore_execution_verified_non_billable' => !empty($stored['restore_execution_verified_non_billable']),
            'verified_only_billing_dry_run_reconciled' => !empty($stored['verified_only_billing_dry_run_reconciled']),
            'production_outputs_unchanged_until_approval' => !empty($stored['production_outputs_unchanged_until_approval']),
        );

        $rows = array();
        $missing = array();
        foreach ($items as $key => $passed) {
            $rows[] = array(
                'key' => sanitize_key((string) $key),
                'status' => $passed ? 'passed' : 'missing',
                'required' => true,
                'source' => isset($stored[$key]) ? $evidence_source : 'contract_readiness',
                'verified_at' => $passed ? $verified_at : '',
            );
            if (!$passed) {
                $missing[] = sanitize_key((string) $key);
            }
        }

        return array(
            'operation' => 'technical_sitemap_authority_staging_evidence_model',
            'status' => empty($missing) && $verified_at !== '' ? 'staging_evidence_complete' : 'staging_evidence_incomplete',
            'dry_run' => true,
            'active' => false,
            'selectable' => false,
            'persisted' => false,
            'source' => $evidence_source,
            'writes_enabled' => false,
            'billing_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'provider_changed' => false,
            'robots_changed' => false,
            'sitemap_written' => false,
            'gsc_submitted' => false,
            'verified_at' => $verified_at,
            'required_count' => count($items),
            'passed_count' => count($items) - count($missing),
            'missing_count' => count($missing),
            'items' => $rows,
            'missing_evidence' => $missing,
            'blockers' => array_values(array_filter(array(
                empty($missing) ? '' : 'staging_evidence_incomplete',
                $verified_at !== '' ? '' : 'staging_verified_at_missing',
            ))),
        );
    }

    private function aysa_technical_sitemap_authority_staging_evidence_dry_run_bridge($project_id, $payload) {
        $project_id = intval($project_id);
        $payload = is_array($payload) ? $payload : array();
        $target_url = isset($payload['target_url']) ? esc_url_raw((string) $payload['target_url']) : '';
        if ($target_url === '') {
            $target_url = $this->aysa_technical_resolve_target_url();
        }
        $audit = $this->aysa_technical_build_sitemap_robots_audit($target_url);
        $backup_id = sanitize_text_field((string) ($payload['backup_id'] ?? ''));
        $backup = $backup_id !== ''
            ? $this->aysa_technical_sitemap_authority_backup_by_id($project_id, $backup_id)
            : $this->aysa_technical_sitemap_authority_latest_matching_backup($project_id, $target_url, $audit);
        $restore_preview = $this->aysa_technical_sitemap_authority_restore_preview($project_id, $backup, $audit);
        $readback_contract = $this->aysa_technical_sitemap_authority_post_takeover_readback_contract($backup, $restore_preview, $audit);
        $post_takeover_readback = $this->aysa_technical_sitemap_authority_evaluate_post_takeover_readback($project_id, $target_url, $backup, $restore_preview, $audit, $readback_contract);
        $restore_execution_contract = $this->aysa_technical_sitemap_authority_restore_execution_contract($backup, $restore_preview, $post_takeover_readback);
        $history_contract = $this->aysa_technical_sitemap_authority_takeover_history_contract($backup, $restore_preview, $post_takeover_readback);
        $billing_contract = $this->aysa_technical_sitemap_authority_billing_contract($backup, $restore_preview, $post_takeover_readback);
        $billing_dry_run = $this->aysa_technical_sitemap_authority_billing_dry_run_reconciliation($billing_contract, $post_takeover_readback);
        $staging_target_url = isset($payload['staging_target_url'])
            ? esc_url_raw((string) $payload['staging_target_url'])
            : esc_url_raw((string) get_option('aysa_sitemap_authority_staging_target_url', ''));
        $staging_project_id = isset($payload['staging_project_id'])
            ? intval($payload['staging_project_id'])
            : intval(get_option('aysa_sitemap_authority_staging_project_id', 0));
        $staging_preflight = $this->aysa_technical_sitemap_authority_staging_preflight_contract($staging_target_url, $staging_project_id);
        $evidence = is_array($payload['evidence'] ?? null) ? $payload['evidence'] : array();
        $staging_evidence = $this->aysa_technical_sitemap_authority_staging_evidence_model($staging_preflight, $backup, $restore_preview, $post_takeover_readback, $history_contract, $evidence, $restore_execution_contract, $billing_dry_run);

        return array(
            'operation' => 'technical_sitemap_authority_staging_evidence_dry_run',
            'status' => (string) ($staging_evidence['status'] ?? 'staging_evidence_incomplete'),
            'project_id' => $project_id,
            'target_url' => $target_url,
            'backup_id' => sanitize_text_field((string) ($backup['backup_id'] ?? $backup_id)),
            'generated_at' => current_time('mysql'),
            'dry_run' => true,
            'active' => false,
            'selectable' => false,
            'persisted' => false,
            'bridge_called' => true,
            'staging_execution_available' => false,
            'staging_verified' => false,
            'production_allowed' => false,
            'writes_enabled' => false,
            'billing_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'provider_changed' => false,
            'robots_changed' => false,
            'sitemap_written' => false,
            'gsc_submitted' => false,
            'staging_preflight' => $staging_preflight,
            'restore_execution_contract' => $restore_execution_contract,
            'billing_contract' => $billing_contract,
            'billing_dry_run_reconciliation' => $billing_dry_run,
            'staging_evidence' => $staging_evidence,
            'blockers' => array_values(array_unique(array_merge(
                is_array($staging_preflight['blockers'] ?? null) ? $staging_preflight['blockers'] : array(),
                is_array($staging_evidence['blockers'] ?? null) ? $staging_evidence['blockers'] : array(),
                array('staging_apply_execution_not_implemented', 'production_approval_missing')
            ))),
        );
    }

    private function aysa_technical_sitemap_authority_production_approval_contract(array $staging_evidence) {
        $approval = get_option('aysa_sitemap_authority_production_approval', array());
        $approval = is_array($approval) ? $approval : array();
        $approved = !empty($approval['approved'])
            && sanitize_key((string) ($approval['approval_scope'] ?? '')) === 'sitemap_authority_production_takeover'
            && !empty($approval['approved_by'])
            && !empty($approval['approved_at']);
        $staging_ready = (string) ($staging_evidence['status'] ?? '') === 'staging_evidence_complete';

        return array(
            'operation' => 'technical_sitemap_authority_production_approval_contract',
            'status' => $approved && $staging_ready ? 'production_approval_ready' : 'production_approval_missing',
            'dry_run' => true,
            'active' => false,
            'selectable' => false,
            'production_allowed' => false,
            'approval_required' => true,
            'approved' => false,
            'approval_scope' => 'sitemap_authority_production_takeover',
            'approved_by' => '',
            'approved_at' => '',
            'requires_staging_evidence_complete' => true,
            'staging_evidence_complete' => $staging_ready,
            'writes_enabled' => false,
            'billing_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'provider_changed' => false,
            'robots_changed' => false,
            'sitemap_written' => false,
            'gsc_submitted' => false,
            'required_confirmation_fields' => array(
                'approval_scope',
                'approved_by',
                'approved_at',
                'target_url',
                'backup_id',
                'candidate_inventory_hash',
                'current_provider_inventory_hash',
                'expected_robots_declaration',
                'restore_plan_acknowledged',
                'billing_after_verified_readback_acknowledged',
            ),
            'blockers' => array_values(array_filter(array(
                $staging_ready ? '' : 'staging_evidence_incomplete',
                $approved ? '' : 'production_approval_missing',
                'production_apply_execution_not_implemented',
            ))),
        );
    }

    private function aysa_technical_sitemap_authority_staging_takeover_contract(array $backup, array $restore_preview = array(), array $readback = array(), array $history_contract = array(), array $restore_execution_contract = array(), array $billing_dry_run = array()) {
        $backup_id = sanitize_text_field((string) ($backup['backup_id'] ?? ''));
        $staging_target_url = esc_url_raw((string) get_option('aysa_sitemap_authority_staging_target_url', ''));
        $staging_project_id = intval(get_option('aysa_sitemap_authority_staging_project_id', 0));
        $staging_verified_at = sanitize_text_field((string) get_option('aysa_sitemap_authority_staging_verified_at', ''));
        $staging_preflight = $this->aysa_technical_sitemap_authority_staging_preflight_contract($staging_target_url, $staging_project_id);
        $staging_evidence = $this->aysa_technical_sitemap_authority_staging_evidence_model($staging_preflight, $backup, $restore_preview, $readback, $history_contract, array(), $restore_execution_contract, $billing_dry_run);
        $production_approval = $this->aysa_technical_sitemap_authority_production_approval_contract($staging_evidence);

        return array(
            'operation' => 'technical_sitemap_authority_staging_takeover_contract',
            'status' => 'staging_required_before_production',
            'backup_id' => $backup_id,
            'dry_run' => true,
            'active' => false,
            'selectable' => false,
            'staging_execution_available' => false,
            'staging_verified' => false,
            'production_allowed' => false,
            'writes_enabled' => false,
            'billing_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'provider_changed' => false,
            'robots_changed' => false,
            'sitemap_written' => false,
            'gsc_submitted' => false,
            'staging_context' => array(
                'target_url' => $staging_target_url,
                'project_id' => $staging_project_id,
                'verified_at' => $staging_verified_at,
            ),
            'staging_preflight' => $staging_preflight,
            'staging_evidence' => $staging_evidence,
            'production_approval' => $production_approval,
            'required_staging_evidence' => array(
                'disposable_or_staging_wordpress_target',
                'provider_detected_before_takeover',
                'candidate_inventory_parity_pass',
                'backup_snapshot_saved',
                'restore_execution_contract_available',
                'active_takeover_history_row_created',
                'robots_readback_matches_aysa_sitemap',
                'sitemap_readback_hash_matches_candidate',
                'restore_execution_verified_non_billable',
                'billing_dry_run_reconciliation_available',
                'verified_only_billing_dry_run_reconciled',
                'production_outputs_unchanged_until_approval',
            ),
            'requires' => array(
                'backup_active' => !empty($backup['active']) && $backup_id !== '',
                'restore_preview_available' => !empty($restore_preview['restore_preview_available']),
                'restore_execution_contract_available' => !empty($restore_execution_contract) && (string) ($restore_execution_contract['operation'] ?? '') === 'technical_sitemap_authority_restore_execution_contract',
                'post_takeover_readback_available' => !empty($readback['readback_available']),
                'history_contract_available' => !empty($history_contract) && (string) ($history_contract['status'] ?? '') !== 'history_contract_missing',
                'billing_dry_run_reconciliation_available' => !empty($billing_dry_run) && (string) ($billing_dry_run['operation'] ?? '') === 'technical_sitemap_authority_billing_dry_run_reconciliation',
                'staging_target_configured' => $staging_target_url !== '' && $staging_project_id > 0,
                'staging_preflight_passed' => (string) ($staging_preflight['status'] ?? '') === 'staging_target_configured',
                'staging_evidence_complete' => (string) ($staging_evidence['status'] ?? '') === 'staging_evidence_complete',
                'production_approval_ready' => (string) ($production_approval['status'] ?? '') === 'production_approval_ready',
                'staging_verified_at_present' => $staging_verified_at !== '',
            ),
            'blockers' => array_values(array_filter(array(
                !empty($backup['active']) && $backup_id !== '' ? '' : 'backup_missing',
                !empty($restore_preview['restore_preview_available']) ? '' : 'restore_preview_missing',
                !empty($readback['readback_available']) ? '' : 'readback_shell_missing',
                !empty($history_contract) ? '' : 'history_contract_missing',
                $staging_target_url !== '' && $staging_project_id > 0 ? '' : 'staging_target_missing',
                (string) ($staging_preflight['status'] ?? '') === 'staging_target_configured' ? '' : 'staging_preflight_blocked',
                (string) ($staging_evidence['status'] ?? '') === 'staging_evidence_complete' ? '' : 'staging_evidence_incomplete',
                (string) ($production_approval['status'] ?? '') === 'production_approval_ready' ? '' : 'production_approval_missing',
                $staging_verified_at !== '' ? '' : 'staging_takeover_not_verified',
            ))),
        );
    }

    private function aysa_technical_sitemap_authority_billing_contract(array $backup, array $restore_preview = array(), array $readback = array()) {
        $backup_active = !empty($backup['active']) && (string) ($backup['backup_id'] ?? '') !== '';
        $restore_preview_available = !empty($restore_preview['restore_preview_available']);
        $restore_execution_available = !empty($restore_preview['restore_execution_available']);
        $readback_verified = !empty($readback['verified']);
        $verified_change_count = intval($readback['verified_change_count'] ?? 0);
        $eligible = $backup_active && $restore_execution_available && $readback_verified && $verified_change_count > 0;

        return array(
            'operation' => 'technical_sitemap_authority_verified_only_billing_contract',
            'status' => $eligible ? 'billing_eligible_after_verified_readback' : 'billing_disabled_until_verified_readback',
            'billing_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'billing_model' => 'future_verified_only_after_readback',
            'unit' => 'verified_sitemap_authority_takeover',
            'currency' => 'credits_cents',
            'estimated_cost_cents' => 0,
            'verified_change_count' => $verified_change_count,
            'eligible_for_billing' => false,
            'idempotency_key' => !empty($backup['backup_id'])
                ? 'sitemap_authority_takeover:' . sanitize_text_field((string) $backup['backup_id'])
                : '',
            'requires' => array(
                'backup_active' => $backup_active,
                'restore_preview_available' => $restore_preview_available,
                'restore_execution_available' => $restore_execution_available,
                'readback_verified' => $readback_verified,
                'verified_change_count_positive' => $verified_change_count > 0,
            ),
            'not_billable_reasons' => array_values(array_filter(array(
                $backup_active ? '' : 'backup_missing',
                $restore_preview_available ? '' : 'restore_preview_missing',
                $restore_execution_available ? '' : 'restore_execution_not_implemented',
                $readback_verified ? '' : 'readback_not_verified',
                $verified_change_count > 0 ? '' : 'no_verified_changes',
                'billing_gate_off',
            ))),
            'charge_timing' => 'after_successful_apply_and_readback_only',
            'never_bill_for' => array(
                'dry_run',
                'backup_creation',
                'restore_preview',
                'restore_execution',
                'failed_readback',
                'no_change',
                'manual_review_needed',
            ),
        );
    }

    private function aysa_technical_sitemap_authority_billing_dry_run_reconciliation(array $billing_contract, array $readback = array()) {
        $idempotency_key = sanitize_text_field((string) ($billing_contract['idempotency_key'] ?? ''));
        $readback_verified = !empty($readback['verified']);
        $verified_change_count = intval($readback['verified_change_count'] ?? 0);
        $eligible = !empty($billing_contract['eligible_for_billing'])
            && $readback_verified
            && $verified_change_count > 0
            && $idempotency_key !== '';

        return array(
            'operation' => 'technical_sitemap_authority_billing_dry_run_reconciliation',
            'status' => $eligible ? 'would_be_eligible_after_gate_enable' : 'not_eligible_without_verified_readback',
            'dry_run' => true,
            'active' => false,
            'selectable' => false,
            'billing_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'debit_allowed' => false,
            'would_charge' => false,
            'eligible_for_billing' => false,
            'idempotency_key' => $idempotency_key,
            'verified_change_count' => $verified_change_count,
            'requires' => array(
                'billing_contract_present' => (string) ($billing_contract['operation'] ?? '') === 'technical_sitemap_authority_verified_only_billing_contract',
                'idempotency_key_present' => $idempotency_key !== '',
                'readback_verified' => $readback_verified,
                'verified_change_count_positive' => $verified_change_count > 0,
                'billing_gate_enabled' => false,
            ),
            'not_billable_reasons' => array_values(array_unique(array_merge(
                is_array($billing_contract['not_billable_reasons'] ?? null) ? $billing_contract['not_billable_reasons'] : array(),
                array_filter(array(
                    $idempotency_key !== '' ? '' : 'billing_idempotency_key_missing',
                    $readback_verified ? '' : 'readback_not_verified',
                    $verified_change_count > 0 ? '' : 'no_verified_changes',
                    'billing_gate_off',
                ))
            ))),
            'charge_timing' => 'after_successful_apply_and_readback_only',
        );
    }

    private function aysa_technical_sitemap_authority_takeover_history_option_key($project_id) {
        return 'aysa_agent8v3_sitemap_authority_takeover_history_' . intval($project_id);
    }

    private function aysa_technical_sitemap_authority_takeover_history_rows($project_id) {
        $rows = get_option($this->aysa_technical_sitemap_authority_takeover_history_option_key($project_id), array());
        return is_array($rows) ? array_values($rows) : array();
    }

    private function aysa_technical_sitemap_authority_runtime_state($project_id = 0, $target_url = '') {
        $project_id = $this->aysa_resolve_project_id_with_local_fallback($project_id);
        $target_url = untrailingslashit(esc_url_raw((string) $target_url));
        if ($target_url === '') {
            $target_url = untrailingslashit(home_url('/'));
        }
        if ($project_id <= 0 || $target_url === '') {
            return array('active' => false, 'verified' => false, 'status' => 'project_or_target_missing');
        }

        foreach (array_reverse($this->aysa_technical_sitemap_authority_takeover_history_rows($project_id)) as $row) {
            $row = is_array($row) ? $row : array();
            $row_target = untrailingslashit(esc_url_raw((string) ($row['target_url'] ?? '')));
            if ($row_target === '' || $row_target !== $target_url) {
                continue;
            }

            $operation = (string) ($row['operation'] ?? '');
            $status = sanitize_key((string) ($row['status'] ?? ''));
            if ($operation === 'technical_sitemap_authority_restore_bridge' && $status === 'restored') {
                return array(
                    'active' => false,
                    'verified' => false,
                    'status' => 'rolled_back',
                    'project_id' => $project_id,
                    'target_url' => $target_url,
                    'history_row' => $row,
                );
            }
            if ($operation !== 'technical_sitemap_authority_apply_bridge') {
                continue;
            }

            $readback = is_array($row['readback_result'] ?? null) ? $row['readback_result'] : array();
            $verified = $status === 'verified'
                && !empty($row['approved_by_user'])
                && !empty($readback['verified'])
                && intval($row['verified_change_count'] ?? $readback['verified_change_count'] ?? 0) > 0
                && empty($row['restored_at'])
                && sanitize_key((string) ($row['restore_status'] ?? '')) !== 'restored';

            return array(
                'active' => $verified,
                'verified' => $verified,
                'status' => $verified ? 'active_verified' : 'inactive_unverified',
                'project_id' => $project_id,
                'target_url' => $target_url,
                'history_row' => $row,
            );
        }

        return array(
            'active' => false,
            'verified' => false,
            'status' => 'inactive_no_takeover_history',
            'project_id' => $project_id,
            'target_url' => $target_url,
        );
    }

    private function aysa_technical_sitemap_authority_runtime_active($project_id = 0, $target_url = '') {
        $state = $this->aysa_technical_sitemap_authority_runtime_state($project_id, $target_url);
        return !empty($state['active']) && !empty($state['verified']);
    }

    private function aysa_technical_sitemap_authority_latest_takeover_history($project_id, $target_url = '') {
        $runtime_state = $this->aysa_technical_sitemap_authority_runtime_state($project_id, $target_url);
        if (empty($runtime_state['active']) || empty($runtime_state['verified'])) {
            return array();
        }
        return is_array($runtime_state['history_row'] ?? null) ? $runtime_state['history_row'] : array();
    }

    private function aysa_technical_sitemap_authority_save_takeover_history_row($project_id, array $row) {
        $rows = $this->aysa_technical_sitemap_authority_takeover_history_rows($project_id);
        $history_id = sanitize_text_field((string) ($row['history_id'] ?? ''));
        if ($history_id !== '') {
            $rows = array_values(array_filter($rows, static function ($existing) use ($history_id) {
                return !is_array($existing) || (string) ($existing['history_id'] ?? '') !== $history_id;
            }));
        }
        $rows[] = $row;
        update_option($this->aysa_technical_sitemap_authority_takeover_history_option_key($project_id), array_slice($rows, -100), false);
    }

    private function aysa_technical_sitemap_authority_gate_option_key($project_id, $gate) {
        $gate = sanitize_key((string) $gate);
        if (!in_array($gate, array('apply', 'restore'), true)) {
            $gate = 'apply';
        }
        return 'aysa_sitemap_authority_' . $gate . '_enabled_project_' . intval($project_id);
    }

    private function aysa_technical_sitemap_authority_apply_gate_enabled($project_id = 0) {
        if ($this->aysa_agent8v3_beta_flag('AYSA_SITEMAP_AUTHORITY_APPLY_ENABLED', false)) {
            return true;
        }
        $project_id = intval($project_id);
        return $project_id > 0 && get_option($this->aysa_technical_sitemap_authority_gate_option_key($project_id, 'apply'), '') === '1';
    }

    private function aysa_technical_sitemap_authority_restore_gate_enabled($project_id = 0) {
        if ($this->aysa_agent8v3_beta_flag('AYSA_SITEMAP_AUTHORITY_RESTORE_ENABLED', false)) {
            return true;
        }
        $project_id = intval($project_id);
        return $project_id > 0 && get_option($this->aysa_technical_sitemap_authority_gate_option_key($project_id, 'restore'), '') === '1';
    }

    public function handle_technical_sitemap_authority_gate() {
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions'), 403);
            return;
        }
        $nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'aysa_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed'), 403);
            return;
        }
        $project_id = $this->aysa_technical_schema_resolve_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Invalid project id'), 422);
            return;
        }
        $gate = sanitize_key((string) wp_unslash($_POST['gate'] ?? 'apply'));
        if (!in_array($gate, array('apply', 'restore'), true)) {
            wp_send_json_error(array('message' => 'Invalid gate'), 422);
            return;
        }
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $enabled_raw = isset($_POST['enabled']) ? strtolower((string) wp_unslash($_POST['enabled'])) : '';
        $enabled = in_array($enabled_raw, array('1', 'true', 'yes', 'on'), true);
        $confirmation = sanitize_text_field((string) wp_unslash($_POST['confirmation'] ?? ''));
        if ($enabled && $confirmation !== 'I understand sitemap authority writes are enabled for this project') {
            wp_send_json_error(array('message' => 'Explicit confirmation required'), 422);
            return;
        }
        $key = $this->aysa_technical_sitemap_authority_gate_option_key($project_id, $gate);
        if ($enabled) {
            update_option($key, '1', false);
        } else {
            delete_option($key);
        }
        wp_send_json_success(array(
            'project_id' => $project_id,
            'gate' => $gate,
            'enabled' => $enabled,
            'apply_enabled' => $this->aysa_technical_sitemap_authority_apply_gate_enabled($project_id),
            'restore_enabled' => $this->aysa_technical_sitemap_authority_restore_gate_enabled($project_id),
            'source' => 'admin_ajax_project_gate',
        ));
    }

    private function aysa_technical_sitemap_authority_apply_bridge($project_id, $payload) {
        $project_id = intval($project_id);
        $payload = is_array($payload) ? $payload : array();
        $target_url = isset($payload['target_url']) ? esc_url_raw((string) $payload['target_url']) : '';
        if ($target_url === '') {
            $target_url = $this->aysa_technical_resolve_target_url();
        }
        $user_confirmed = !empty($payload['user_confirmed']) || !empty($payload['confirmed_by_user']);
        if (!$this->aysa_technical_sitemap_authority_apply_gate_enabled($project_id) || !$user_confirmed) {
            $disabled = $this->aysa_technical_sitemap_authority_apply_bridge_disabled($project_id, $payload);
            if (!$user_confirmed) {
                $disabled['status'] = 'explicit_confirmation_required';
                $disabled['blockers'] = array_values(array_unique(array_merge(
                    is_array($disabled['blockers'] ?? null) ? $disabled['blockers'] : array(),
                    array('explicit_confirmation_required')
                )));
            }
            if (!$this->aysa_technical_sitemap_authority_apply_gate_enabled($project_id)) {
                $disabled['gate'] = 'AYSA_SITEMAP_AUTHORITY_APPLY_ENABLED';
            }
            return $disabled;
        }

        $before_audit = $this->aysa_technical_build_sitemap_robots_audit($target_url);
        $backup_id = sanitize_text_field((string) ($payload['backup_id'] ?? ''));
        $backup = $backup_id !== ''
            ? $this->aysa_technical_sitemap_authority_backup_by_id($project_id, $backup_id)
            : array();
        if ($backup === array()) {
            $backup = $this->aysa_technical_sitemap_authority_create_backup_snapshot($project_id, $target_url, $before_audit);
        }
        $restore_preview_before = $this->aysa_technical_sitemap_authority_restore_preview($project_id, $backup, $before_audit);
        $readback_contract_before = $this->aysa_technical_sitemap_authority_post_takeover_readback_contract($backup, $restore_preview_before, $before_audit);
        $future_sitemap = esc_url_raw((string) ($readback_contract_before['expected_after_apply']['future_sitemap_index_url'] ?? ($backup['future_sitemap_index_url'] ?? $this->aysa_technical_preferred_sitemap_url($target_url))));
        $parity = is_array($before_audit['sitemap_authority_dry_run_bridge']['parity'] ?? null)
            ? $before_audit['sitemap_authority_dry_run_bridge']['parity']
            : $this->aysa_technical_sitemap_authority_parity_summary(
                is_array($before_audit['current_provider_inventory'] ?? null) ? $before_audit['current_provider_inventory'] : array(),
                is_array($before_audit['aysa_candidate_inventory'] ?? null) ? $before_audit['aysa_candidate_inventory'] : array(),
                is_array($before_audit['sitemap_parity_diff'] ?? null) ? $before_audit['sitemap_parity_diff'] : array()
            );
        if ((string) ($parity['parity_status'] ?? '') !== 'parity_pass') {
            $disabled = $this->aysa_technical_sitemap_authority_apply_bridge_disabled($project_id, array_merge($payload, array('backup_id' => (string) ($backup['backup_id'] ?? ''))));
            $disabled['status'] = 'apply_blocked_by_parity';
            $disabled['backup_snapshot'] = $backup;
            $disabled['parity'] = $parity;
            $disabled['blockers'] = array_values(array_unique(array_merge(
                is_array($disabled['blockers'] ?? null) ? $disabled['blockers'] : array(),
                array('parity_not_passed')
            )));
            return $disabled;
        }

        $generation = $this->aysa_technical_generate_own_sitemap($target_url);
        $robots_content = $this->aysa_technical_robots_default_content($target_url, $future_sitemap);
        $robots_save = $this->aysa_technical_save_robots_content($robots_content);
        $robots_saved = !is_wp_error($robots_save);
        $after_audit = $this->aysa_technical_build_sitemap_robots_audit($target_url);
        update_option($this->aysa_technical_sitemap_robots_state_key($project_id), array(
            'project_id' => $project_id,
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'data' => $after_audit,
        ), false);

        $restore_preview_after = $this->aysa_technical_sitemap_authority_restore_preview($project_id, $backup, $after_audit);
        $readback_contract = $this->aysa_technical_sitemap_authority_post_takeover_readback_contract($backup, $restore_preview_after, $after_audit);
        $readback = $this->aysa_technical_sitemap_authority_evaluate_post_takeover_readback($project_id, $target_url, $backup, $restore_preview_after, $after_audit, $readback_contract, true);
        $verified = !empty($readback['verified']);
        $history_id = 'sitemap_authority_takeover_' . substr(hash('sha256', (string) ($backup['backup_id'] ?? '') . '|' . current_time('mysql') . '|' . wp_salt('auth')), 0, 20);
        $history_row = array(
            'history_id' => $history_id,
            'backup_id' => sanitize_text_field((string) ($backup['backup_id'] ?? '')),
            'project_id' => $project_id,
            'website_id' => intval(get_option('aysa_website_id', 0)),
            'target_url' => $target_url,
            'operation' => 'technical_sitemap_authority_apply_bridge',
            'approved_by_user' => true,
            'approved_at' => current_time('mysql'),
            'status' => $verified ? 'verified' : 'completed_with_warnings',
            'current_provider_before' => sanitize_text_field((string) ($backup['current_provider'] ?? 'unknown')),
            'future_provider_after' => 'AYSA',
            'robots_before_hash' => sanitize_text_field((string) ($backup['robots_before_hash'] ?? '')),
            'robots_after_hash' => sanitize_text_field((string) ($readback['live_readback']['robots_current_hash'] ?? '')),
            'current_provider_inventory_hash' => sanitize_text_field((string) ($backup['current_provider_inventory_hash'] ?? '')),
            'candidate_inventory_hash' => sanitize_text_field((string) ($backup['candidate_inventory_hash'] ?? '')),
            'apply_result' => array(
                'sitemap_generated' => !empty($generation['saved']) || intval($generation['entries_count'] ?? 0) > 0,
                'generation_mode' => sanitize_text_field((string) ($generation['mode'] ?? '')),
                'entries_count' => intval($generation['entries_count'] ?? 0),
                'children_count' => intval($generation['children_count'] ?? 0),
                'robots_saved' => $robots_saved,
                'robots_error' => is_wp_error($robots_save) ? $robots_save->get_error_message() : '',
            ),
            'readback_result' => $readback,
            'verified_change_count' => intval($readback['verified_change_count'] ?? 0),
            'billing_idempotency_key' => !empty($backup['backup_id']) ? 'sitemap_authority_takeover:' . sanitize_text_field((string) $backup['backup_id']) : '',
            'restore_status' => !empty($backup['robots_before_content']) ? 'restore_available' : 'restore_requires_backup_content',
            'billing_enabled' => false,
            'created_at' => current_time('mysql'),
        );
        $this->aysa_technical_sitemap_authority_save_takeover_history_row($project_id, $history_row);

        $billing_contract = $this->aysa_technical_sitemap_authority_billing_contract($backup, $restore_preview_after, $readback);
        $billing_dry_run = $this->aysa_technical_sitemap_authority_billing_dry_run_reconciliation($billing_contract, $readback);
        return array(
            'operation' => 'technical_sitemap_authority_apply_bridge',
            'status' => $verified ? 'verified' : 'completed_with_warnings',
            'project_id' => $project_id,
            'target_url' => $target_url,
            'backup_id' => sanitize_text_field((string) ($backup['backup_id'] ?? '')),
            'history_id' => $history_id,
            'generated_at' => current_time('mysql'),
            'dry_run' => false,
            'active' => true,
            'selectable' => false,
            'explicit_confirmation_required' => true,
            'user_confirmed' => $user_confirmed,
            'apply_execution_available' => true,
            'restore_execution_available' => !empty($backup['robots_before_content']),
            'restore_available' => !empty($backup['robots_before_content']),
            'backup_snapshot' => $backup,
            'restore_preview' => $restore_preview_after,
            'post_takeover_readback_contract' => $readback_contract,
            'post_takeover_readback' => $readback,
            'takeover_history_row' => $history_row,
            'takeover_history_contract' => $this->aysa_technical_sitemap_authority_takeover_history_contract($backup, $restore_preview_after, $readback),
            'billing_contract' => $billing_contract,
            'billing_dry_run_reconciliation' => $billing_dry_run,
            'writes_enabled' => true,
            'billing_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'provider_changed' => true,
            'robots_changed' => $robots_saved,
            'sitemap_written' => !empty($generation['saved']) || intval($generation['entries_count'] ?? 0) > 0,
            'gsc_submitted' => false,
            'generation' => $generation,
            'blockers' => $verified ? array('billing_gate_off') : array_values(array_unique(array_merge(array('billing_gate_off'), is_array($readback['failed_checks'] ?? null) ? $readback['failed_checks'] : array()))),
        );
    }

    private function aysa_technical_sitemap_authority_restore_bridge($project_id, $payload) {
        $project_id = intval($project_id);
        $payload = is_array($payload) ? $payload : array();
        $target_url = isset($payload['target_url']) ? esc_url_raw((string) $payload['target_url']) : '';
        if ($target_url === '') {
            $target_url = $this->aysa_technical_resolve_target_url();
        }
        $user_confirmed = !empty($payload['user_confirmed']) || !empty($payload['confirmed_by_user']);
        if (!$this->aysa_technical_sitemap_authority_restore_gate_enabled($project_id) || !$user_confirmed) {
            $disabled = $this->aysa_technical_sitemap_authority_restore_bridge_disabled($project_id, $payload);
            if (!$user_confirmed) {
                $disabled['status'] = 'explicit_confirmation_required';
                $disabled['blockers'] = array_values(array_unique(array_merge(
                    is_array($disabled['blockers'] ?? null) ? $disabled['blockers'] : array(),
                    array('explicit_confirmation_required')
                )));
            }
            if (!$this->aysa_technical_sitemap_authority_restore_gate_enabled($project_id)) {
                $disabled['gate'] = 'AYSA_SITEMAP_AUTHORITY_RESTORE_ENABLED';
            }
            return $disabled;
        }
        $audit = $this->aysa_technical_build_sitemap_robots_audit($target_url);
        $backup_id = sanitize_text_field((string) ($payload['backup_id'] ?? ''));
        $backup = $backup_id !== ''
            ? $this->aysa_technical_sitemap_authority_backup_by_id($project_id, $backup_id)
            : $this->aysa_technical_sitemap_authority_latest_matching_backup($project_id, $target_url, $audit);
        if ($backup === array() || empty($backup['robots_before_content'])) {
            $disabled = $this->aysa_technical_sitemap_authority_restore_bridge_disabled($project_id, $payload);
            $disabled['status'] = 'restore_backup_content_missing';
            $disabled['blockers'] = array_values(array_unique(array_merge(
                is_array($disabled['blockers'] ?? null) ? $disabled['blockers'] : array(),
                array('restore_backup_content_missing')
            )));
            return $disabled;
        }
        $saved = $this->aysa_technical_save_robots_content((string) $backup['robots_before_content']);
        $after_audit = $this->aysa_technical_build_sitemap_robots_audit($target_url);
        update_option($this->aysa_technical_sitemap_robots_state_key($project_id), array(
            'project_id' => $project_id,
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'data' => $after_audit,
        ), false);
        $robots = is_array($after_audit['robots'] ?? null) ? $after_audit['robots'] : array();
        $robots_content = (string) ($robots['content'] ?? '');
        $restored_hash = $robots_content !== '' ? hash('sha256', $robots_content) : '';
        $verified = !is_wp_error($saved) && $restored_hash !== '' && hash_equals((string) ($backup['robots_before_hash'] ?? ''), $restored_hash);
        $history_id = 'sitemap_authority_restore_' . substr(hash('sha256', (string) ($backup['backup_id'] ?? '') . '|' . current_time('mysql') . '|' . wp_salt('auth')), 0, 20);
        if ($verified) {
            $this->aysa_technical_sitemap_authority_save_takeover_history_row($project_id, array(
                'history_id' => $history_id,
                'backup_id' => sanitize_text_field((string) ($backup['backup_id'] ?? '')),
                'project_id' => $project_id,
                'website_id' => intval(get_option('aysa_website_id', 0)),
                'target_url' => $target_url,
                'operation' => 'technical_sitemap_authority_restore_bridge',
                'approved_by_user' => true,
                'approved_at' => current_time('mysql'),
                'status' => 'restored',
                'restored_at' => current_time('mysql'),
                'restore_status' => 'restored',
                'readback_result' => array(
                    'verified' => true,
                    'robots_after_hash' => $restored_hash,
                    'robots_before_hash' => sanitize_text_field((string) ($backup['robots_before_hash'] ?? '')),
                ),
                'billing_enabled' => false,
                'billing_status' => 'not_billable_restore',
                'created_at' => current_time('mysql'),
            ));
        }
        return array(
            'operation' => 'technical_sitemap_authority_restore_bridge',
            'status' => $verified ? 'restored' : 'restore_readback_mismatch',
            'project_id' => $project_id,
            'target_url' => $target_url,
            'backup_id' => sanitize_text_field((string) ($backup['backup_id'] ?? '')),
            'history_id' => $verified ? $history_id : '',
            'generated_at' => current_time('mysql'),
            'dry_run' => false,
            'active' => true,
            'selectable' => false,
            'restore_execution_available' => true,
            'restore_available' => true,
            'restore_status' => $verified ? 'restored' : 'restore_readback_mismatch',
            'writes_enabled' => true,
            'billing_enabled' => false,
            'billing_status' => 'not_billable_restore',
            'provider_changed' => false,
            'robots_changed' => !is_wp_error($saved),
            'sitemap_written' => false,
            'gsc_submitted' => false,
            'readback' => array(
                'robots_after_hash' => $restored_hash,
                'robots_before_hash' => sanitize_text_field((string) ($backup['robots_before_hash'] ?? '')),
                'verified' => $verified,
            ),
            'blockers' => $verified ? array() : array('restore_readback_mismatch'),
        );
    }

    private function aysa_technical_sitemap_authority_restore_bridge_disabled($project_id, $payload) {
        $project_id = intval($project_id);
        $payload = is_array($payload) ? $payload : array();
        $target_url = isset($payload['target_url']) ? esc_url_raw((string) $payload['target_url']) : '';
        if ($target_url === '') {
            $target_url = $this->aysa_technical_resolve_target_url();
        }
        $audit = $this->aysa_technical_build_sitemap_robots_audit($target_url);
        $backup_id = sanitize_text_field((string) ($payload['backup_id'] ?? ''));
        $backup = $backup_id !== ''
            ? $this->aysa_technical_sitemap_authority_backup_by_id($project_id, $backup_id)
            : $this->aysa_technical_sitemap_authority_latest_matching_backup($project_id, $target_url, $audit);
        $preview = $this->aysa_technical_sitemap_authority_restore_preview($project_id, $backup, $audit);
        $readback_contract = $this->aysa_technical_sitemap_authority_post_takeover_readback_contract($backup, $preview, $audit);
        $post_takeover_readback = $this->aysa_technical_sitemap_authority_evaluate_post_takeover_readback($project_id, $target_url, $backup, $preview, $audit, $readback_contract);
        $restore_execution_contract = $this->aysa_technical_sitemap_authority_restore_execution_contract($backup, $preview, $post_takeover_readback);
        $history_contract = $this->aysa_technical_sitemap_authority_takeover_history_contract($backup, $preview, $post_takeover_readback);
        $billing_contract = $this->aysa_technical_sitemap_authority_billing_contract($backup, $preview, $post_takeover_readback);
        $billing_dry_run = $this->aysa_technical_sitemap_authority_billing_dry_run_reconciliation($billing_contract, $post_takeover_readback);
        $staging_contract = $this->aysa_technical_sitemap_authority_staging_takeover_contract($backup, $preview, $post_takeover_readback, $history_contract, $restore_execution_contract, $billing_dry_run);

        return array(
            'operation' => 'technical_sitemap_authority_restore_bridge',
            'status' => 'restore_bridge_disabled_for_rollout',
            'project_id' => $project_id,
            'target_url' => $target_url,
            'backup_id' => sanitize_text_field((string) ($backup['backup_id'] ?? $backup_id)),
            'generated_at' => current_time('mysql'),
            'dry_run' => true,
            'active' => false,
            'selectable' => false,
            'restore_preview' => $preview,
            'restore_execution_contract' => $restore_execution_contract,
            'restore_execution_available' => false,
            'restore_available' => false,
            'restore_status' => 'restore_execution_not_implemented',
            'post_takeover_readback_contract' => $readback_contract,
            'post_takeover_readback' => $post_takeover_readback,
            'takeover_history_contract' => $history_contract,
            'staging_takeover_contract' => $staging_contract,
            'billing_contract' => $billing_contract,
            'billing_dry_run_reconciliation' => $billing_dry_run,
            'writes_enabled' => false,
            'billing_enabled' => false,
            'billing_status' => 'not_billable_restore',
            'provider_changed' => false,
            'robots_changed' => false,
            'sitemap_written' => false,
            'gsc_submitted' => false,
            'blockers' => array(
                'restore_execution_not_implemented',
                'readback_execution_not_implemented',
                'history_execution_not_implemented',
                'apply_gate_off',
                'active_apply_bridge_missing',
                'staging_takeover_not_verified',
                'verified_only_billing_missing',
            ),
        );
    }

    private function aysa_technical_sitemap_authority_apply_bridge_disabled($project_id, $payload) {
        $project_id = intval($project_id);
        $payload = is_array($payload) ? $payload : array();
        $target_url = isset($payload['target_url']) ? esc_url_raw((string) $payload['target_url']) : '';
        if ($target_url === '') {
            $target_url = $this->aysa_technical_resolve_target_url();
        }
        $audit = $this->aysa_technical_build_sitemap_robots_audit($target_url);
        $backup_id = sanitize_text_field((string) ($payload['backup_id'] ?? ''));
        $backup = $backup_id !== ''
            ? $this->aysa_technical_sitemap_authority_backup_by_id($project_id, $backup_id)
            : $this->aysa_technical_sitemap_authority_latest_matching_backup($project_id, $target_url, $audit);
        $restore_preview = $this->aysa_technical_sitemap_authority_restore_preview($project_id, $backup, $audit);
        $readback_contract = $this->aysa_technical_sitemap_authority_post_takeover_readback_contract($backup, $restore_preview, $audit);
        $post_takeover_readback = $this->aysa_technical_sitemap_authority_evaluate_post_takeover_readback($project_id, $target_url, $backup, $restore_preview, $audit, $readback_contract);
        $restore_execution_contract = $this->aysa_technical_sitemap_authority_restore_execution_contract($backup, $restore_preview, $post_takeover_readback);
        $history_contract = $this->aysa_technical_sitemap_authority_takeover_history_contract($backup, $restore_preview, $post_takeover_readback);
        $billing_contract = $this->aysa_technical_sitemap_authority_billing_contract($backup, $restore_preview, $post_takeover_readback);
        $billing_dry_run = $this->aysa_technical_sitemap_authority_billing_dry_run_reconciliation($billing_contract, $post_takeover_readback);
        $staging_contract = $this->aysa_technical_sitemap_authority_staging_takeover_contract($backup, $restore_preview, $post_takeover_readback, $history_contract, $restore_execution_contract, $billing_dry_run);

        return array(
            'operation' => 'technical_sitemap_authority_apply_bridge',
            'status' => 'apply_bridge_disabled_for_rollout',
            'project_id' => $project_id,
            'target_url' => $target_url,
            'backup_id' => sanitize_text_field((string) ($backup['backup_id'] ?? $backup_id)),
            'generated_at' => current_time('mysql'),
            'dry_run' => true,
            'active' => false,
            'selectable' => false,
            'explicit_confirmation_required' => true,
            'user_confirmed' => !empty($payload['user_confirmed']) || !empty($payload['confirmed_by_user']),
            'apply_execution_available' => false,
            'restore_execution_available' => false,
            'restore_preview' => $restore_preview,
            'restore_execution_contract' => $restore_execution_contract,
            'post_takeover_readback_contract' => $readback_contract,
            'post_takeover_readback' => $post_takeover_readback,
            'takeover_history_contract' => $history_contract,
            'staging_takeover_contract' => $staging_contract,
            'billing_contract' => $billing_contract,
            'billing_dry_run_reconciliation' => $billing_dry_run,
            'writes_enabled' => false,
            'billing_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'provider_changed' => false,
            'robots_changed' => false,
            'sitemap_written' => false,
            'gsc_submitted' => false,
            'blockers' => array(
                'apply_execution_not_implemented',
                'apply_gate_off',
                'restore_execution_not_implemented',
                'readback_execution_not_implemented',
                'history_execution_not_implemented',
                'verified_only_billing_missing',
                'staging_takeover_not_verified',
                'production_approval_missing',
            ),
        );
    }

    private function aysa_technical_build_sitemap_authority_dry_run_contract($project_id, $target_url, array $audit) {
        $project_id = intval($project_id);
        $target_url = esc_url_raw((string) $target_url);
        $current_provider_inventory = is_array($audit['current_provider_inventory'] ?? null) ? $audit['current_provider_inventory'] : array();
        $aysa_candidate_inventory = is_array($audit['aysa_candidate_inventory'] ?? null) ? $audit['aysa_candidate_inventory'] : array();
        $sitemap_parity_diff = is_array($audit['sitemap_parity_diff'] ?? null) ? $audit['sitemap_parity_diff'] : array();
        $parity = $this->aysa_technical_sitemap_authority_parity_summary($current_provider_inventory, $aysa_candidate_inventory, $sitemap_parity_diff);
        $parity_decision = $this->aysa_technical_sitemap_authority_parity_decision($parity, $sitemap_parity_diff);
        $providers = is_array($audit['providers'] ?? null) ? $audit['providers'] : array();
        $primary_provider = sanitize_text_field((string) ($providers[0] ?? ($current_provider_inventory['detected_provider'] ?? 'unknown')));
        $blockers = array();

        if ((string) ($parity['parity_status'] ?? '') !== 'parity_pass') {
            $blockers[] = 'parity_not_passed';
        }
        if (intval($current_provider_inventory['url_count'] ?? 0) <= 0) {
            $blockers[] = 'current_provider_inventory_missing';
        }
        if (intval($aysa_candidate_inventory['candidate_url_count'] ?? 0) <= 0) {
            $blockers[] = 'candidate_sitemap_inventory_missing';
        }
        if ($primary_provider === '' || strtolower($primary_provider) === 'unknown') {
            $blockers[] = 'provider_ownership_unresolved';
        }
        $blockers[] = 'backup_restore_not_active';
        $blockers[] = 'apply_gate_off';
        $blockers[] = 'active_apply_bridge_missing';
        $blockers[] = 'readback_execution_not_implemented';
        $blockers[] = 'verified_only_billing_missing';
        $blockers = array_values(array_unique($blockers));

        $status = ($blockers === array() && (string) ($parity['parity_status'] ?? '') === 'parity_pass')
            ? 'validated'
            : (((string) ($parity['parity_status'] ?? '') === 'insufficient_data') ? 'manual_review_needed' : 'blocked');
        $robots = is_array($audit['robots'] ?? null) ? $audit['robots'] : array();
        $robots_content = (string) ($robots['content'] ?? '');
        $current_sitemap = esc_url_raw((string) ($current_provider_inventory['sitemap_index_url'] ?? ($audit['primary_sitemap'] ?? '')));
        $future_sitemap = esc_url_raw((string) ($aysa_candidate_inventory['candidate_sitemap_index_url'] ?? $this->aysa_technical_preferred_sitemap_url($target_url)));

        return array(
            'operation' => 'technical_sitemap_authority_dry_run_bridge',
            'status' => $status,
            'project_id' => $project_id,
            'target_url' => $target_url,
            'generated_at' => current_time('mysql'),
            'dry_run' => true,
            'bridge_called' => false,
            'active' => false,
            'selectable' => false,
            'writes_enabled' => false,
            'billing_enabled' => false,
            'provider_changed' => false,
            'robots_changed' => false,
            'sitemap_written' => false,
            'gsc_submitted' => false,
            'current_provider' => $primary_provider,
            'future_provider' => 'AYSA',
            'current_sitemap_index_url' => $current_sitemap,
            'future_sitemap_index_url' => $future_sitemap,
            'current_provider_inventory_hash' => sanitize_text_field((string) ($current_provider_inventory['url_list_hash'] ?? '')),
            'candidate_inventory_hash' => sanitize_text_field((string) ($aysa_candidate_inventory['candidate_url_list_hash'] ?? '')),
            'robots_before_hash' => $robots_content !== '' ? hash('sha256', $robots_content) : '',
            'robots_future_declaration' => $future_sitemap !== '' ? 'Sitemap: ' . $future_sitemap : '',
            'parity' => $parity,
            'parity_decision' => $parity_decision,
            'residual_classification' => is_array($sitemap_parity_diff['residual_classification'] ?? null) ? $sitemap_parity_diff['residual_classification'] : array(),
            'blockers' => $blockers,
            'backup_contract' => array(
                'required' => true,
                'active' => false,
                'must_capture' => array(
                    'robots_content_hash',
                    'current_provider_sitemap_hash',
                    'current_child_sitemaps',
                    'provider_plugin_identity',
                    'candidate_inventory_hash',
                ),
            ),
            'restore_contract' => array(
                'required' => true,
                'active' => false,
                'non_billable' => true,
                'status' => 'missing_active_restore',
            ),
            'readback_contract' => array(
                'required' => true,
                'active' => false,
                'future_checks' => array(
                    'robots_contains_future_sitemap',
                    'future_sitemap_http_200',
                    'future_sitemap_hash_matches_candidate',
                    'legacy_provider_not_silently_broken',
                ),
            ),
            'billing_contract' => array(
                'required' => true,
                'active' => false,
                'model' => 'future_verified_only_after_readback',
            ),
        );
    }

    private function aysa_technical_sitemap_authority_lifecycle_gate_summary(array $contract, array $backup, array $restore_preview, array $readback, array $history_contract, array $staging_contract, array $billing_contract) {
        $parity = is_array($contract['parity'] ?? null) ? $contract['parity'] : array();
        $parity_decision = is_array($contract['parity_decision'] ?? null) ? $contract['parity_decision'] : array();
        $staging_evidence = is_array($staging_contract['staging_evidence'] ?? null) ? $staging_contract['staging_evidence'] : array();
        $production_approval = is_array($staging_contract['production_approval'] ?? null) ? $staging_contract['production_approval'] : array();
        $gate_phases = array(
            'inventory_parity' => array('provider_inventory', 'candidate_inventory', 'parity_pass'),
            'backup_restore' => array('backup_active', 'restore_preview_available', 'restore_execution_available'),
            'readback_history' => array('readback_available', 'readback_verified', 'history_contract_available'),
            'staging' => array('staging_evidence_complete'),
            'production_approval' => array('production_approval_ready'),
            'billing' => array('verified_only_billing_ready'),
        );
        $gates = array(
            'provider_inventory' => intval($contract['snapshot']['provider_url_count'] ?? 0) > 0,
            'candidate_inventory' => intval($contract['snapshot']['candidate_url_count'] ?? 0) > 0,
            'parity_pass' => (string) ($parity['parity_status'] ?? '') === 'parity_pass',
            'backup_active' => !empty($backup['active']),
            'restore_preview_available' => !empty($restore_preview['restore_preview_available']),
            'restore_execution_available' => !empty($restore_preview['restore_execution_available']),
            'readback_available' => !empty($readback['readback_available']),
            'readback_verified' => !empty($readback['verified']),
            'history_contract_available' => !empty($history_contract),
            'staging_evidence_complete' => (string) ($staging_evidence['status'] ?? '') === 'staging_evidence_complete',
            'production_approval_ready' => (string) ($production_approval['status'] ?? '') === 'production_approval_ready',
            'verified_only_billing_ready' => !empty($billing_contract['eligible_for_billing']),
        );
        $missing = array();
        foreach ($gates as $key => $passed) {
            if (!$passed) {
                $missing[] = sanitize_key((string) $key);
            }
        }
        $phase_rows = array();
        $next_phase = '';
        foreach ($gate_phases as $phase_key => $phase_gates) {
            $phase_missing = array();
            $phase_passed = 0;
            foreach ($phase_gates as $gate_key) {
                if (!empty($gates[$gate_key])) {
                    $phase_passed++;
                } else {
                    $phase_missing[] = sanitize_key((string) $gate_key);
                }
            }
            if ($next_phase === '' && $phase_missing !== array()) {
                $next_phase = sanitize_key((string) $phase_key);
            }
            $phase_rows[] = array(
                'phase' => sanitize_key((string) $phase_key),
                'status' => $phase_missing === array() ? 'passed' : 'blocked',
                'passed_count' => $phase_passed,
                'required_count' => count($phase_gates),
                'missing_gates' => $phase_missing,
            );
        }

        $next_step = empty($missing)
            ? 'define_controlled_staging_apply_contract'
            : 'resolve_' . sanitize_key((string) $missing[0]);
        if (in_array('parity_pass', $missing, true) && (string) ($parity_decision['next_step'] ?? '') !== '') {
            $next_step = sanitize_key((string) $parity_decision['next_step']);
        }

        return array(
            'operation' => 'technical_sitemap_authority_lifecycle_gate_summary',
            'status' => empty($missing) ? 'ready_for_controlled_apply_contract' : 'blocked',
            'dry_run' => true,
            'active' => false,
            'selectable' => false,
            'writes_enabled' => false,
            'billing_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'provider_changed' => false,
            'robots_changed' => false,
            'sitemap_written' => false,
            'gsc_submitted' => false,
            'passed_count' => count($gates) - count($missing),
            'required_count' => count($gates),
            'gates' => $gates,
            'gate_phases' => $phase_rows,
            'missing_gates' => $missing,
            'next_phase' => $next_phase,
            'parity_decision' => $parity_decision,
            'next_blocker' => (string) ($missing[0] ?? ''),
            'next_step' => $next_step,
        );
    }

	    private function aysa_technical_sitemap_authority_dry_run_bridge($project_id, $payload) {
	        $project_id = intval($project_id);
	        $payload = is_array($payload) ? $payload : array();
	        $target_url = isset($payload['target_url']) ? esc_url_raw((string) $payload['target_url']) : '';
	        if ($target_url === '') {
	            $target_url = $this->aysa_technical_resolve_target_url();
	        }
	        $audit = $this->aysa_technical_build_sitemap_robots_audit($target_url);
	        return $this->aysa_technical_sitemap_authority_dry_run_bridge_from_audit($project_id, $target_url, $audit);
	    }

	    private function aysa_technical_sitemap_authority_dry_run_bridge_from_audit($project_id, $target_url, array $audit) {
	        $project_id = intval($project_id);
	        $target_url = esc_url_raw((string) $target_url);
	        $backup = $this->aysa_technical_sitemap_authority_create_backup_snapshot($project_id, $target_url, $audit);
        $restore_preview = $this->aysa_technical_sitemap_authority_restore_preview($project_id, $backup, $audit);
        $readback_contract = $this->aysa_technical_sitemap_authority_post_takeover_readback_contract($backup, $restore_preview, $audit);
        $post_takeover_readback = $this->aysa_technical_sitemap_authority_evaluate_post_takeover_readback($project_id, $target_url, $backup, $restore_preview, $audit, $readback_contract);
        $restore_execution_contract = $this->aysa_technical_sitemap_authority_restore_execution_contract($backup, $restore_preview, $post_takeover_readback);
        $history_contract = $this->aysa_technical_sitemap_authority_takeover_history_contract($backup, $restore_preview, $post_takeover_readback);
        $billing_contract = $this->aysa_technical_sitemap_authority_billing_contract($backup, $restore_preview, $post_takeover_readback);
        $billing_dry_run = $this->aysa_technical_sitemap_authority_billing_dry_run_reconciliation($billing_contract, $post_takeover_readback);
        $staging_contract = $this->aysa_technical_sitemap_authority_staging_takeover_contract($backup, $restore_preview, $post_takeover_readback, $history_contract, $restore_execution_contract, $billing_dry_run);
        $contract = $this->aysa_technical_build_sitemap_authority_dry_run_contract($project_id, $target_url, $audit);
        $contract['bridge_called'] = true;
        $contract['backup_snapshot'] = $backup;
        $contract['restore_preview'] = $restore_preview;
        $contract['restore_execution_contract'] = $restore_execution_contract;
        $contract['post_takeover_readback_contract'] = $readback_contract;
        $contract['post_takeover_readback'] = $post_takeover_readback;
        $contract['takeover_history_contract'] = $history_contract;
        $contract['staging_takeover_contract'] = $staging_contract;
        $contract['billing_contract'] = $billing_contract;
        $contract['billing_dry_run_reconciliation'] = $billing_dry_run;
        $contract['restore_bridge_contract'] = array(
            'operation' => 'technical_sitemap_authority_restore_bridge',
            'status' => 'restore_bridge_disabled_for_rollout',
            'backup_id' => sanitize_text_field((string) ($backup['backup_id'] ?? '')),
            'dry_run' => true,
            'active' => false,
            'selectable' => false,
            'restore_execution_contract' => $restore_execution_contract,
            'restore_execution_available' => false,
            'restore_available' => false,
            'writes_enabled' => false,
            'billing_enabled' => false,
            'billing_status' => 'not_billable_restore',
            'billing_contract' => $billing_contract,
            'billing_dry_run_reconciliation' => $billing_dry_run,
            'blockers' => array(
                'restore_execution_not_implemented',
                'readback_execution_not_implemented',
                'history_execution_not_implemented',
                'apply_gate_off',
                'active_apply_bridge_missing',
                'staging_takeover_not_verified',
                'verified_only_billing_missing',
            ),
        );
        $apply_gate_enabled = $this->aysa_technical_sitemap_authority_apply_gate_enabled($project_id);
        $restore_gate_enabled = $this->aysa_technical_sitemap_authority_restore_gate_enabled($project_id);
        $parity_passed = (string) ($contract['parity']['parity_status'] ?? '') === 'parity_pass';
        $apply_ready = $apply_gate_enabled && $restore_gate_enabled && $parity_passed && !empty($backup['active']) && !empty($backup['restore_available']);
        $apply_blockers = array();
        if (!$apply_gate_enabled) {
            $apply_blockers[] = 'apply_gate_off';
        }
        if (!$restore_gate_enabled) {
            $apply_blockers[] = 'restore_gate_off';
        }
        if (!$parity_passed) {
            $apply_blockers[] = 'parity_not_passed';
        }
        if (empty($backup['active'])) {
            $apply_blockers[] = 'backup_not_active';
        }
        if (empty($backup['restore_available'])) {
            $apply_blockers[] = 'restore_snapshot_missing';
        }
        $apply_blockers[] = 'verified_only_billing_pending_activation';

        $contract['apply_bridge_contract'] = array(
            'operation' => 'technical_sitemap_authority_apply_bridge',
            'status' => $apply_ready ? 'ready_for_controlled_apply' : 'apply_bridge_disabled_for_rollout',
            'backup_id' => sanitize_text_field((string) ($backup['backup_id'] ?? '')),
            'dry_run' => !$apply_ready,
            'active' => false,
            'selectable' => $apply_ready,
            'explicit_confirmation_required' => true,
            'apply_gate_enabled' => $apply_gate_enabled,
            'restore_gate_enabled' => $restore_gate_enabled,
            'apply_execution_available' => $apply_ready,
            'restore_execution_available' => $restore_gate_enabled && !empty($backup['restore_available']),
            'writes_enabled' => $apply_ready,
            'billing_enabled' => false,
            'billing_status' => 'disabled_for_rollout',
            'billing_contract' => $billing_contract,
            'billing_dry_run_reconciliation' => $billing_dry_run,
            'provider_changed' => false,
            'robots_changed' => false,
            'sitemap_written' => false,
            'gsc_submitted' => false,
            'blockers' => array_values(array_unique($apply_blockers)),
        );
        $contract['backup_contract']['active'] = !empty($backup['active']);
        $contract['backup_contract']['backup_id'] = sanitize_text_field((string) ($backup['backup_id'] ?? ''));
        $contract['backup_contract']['status'] = sanitize_key((string) ($backup['status'] ?? 'backup_missing'));
        if (!empty($backup['active'])) {
            $contract['blockers'] = array_values(array_diff((array) ($contract['blockers'] ?? array()), array('backup_restore_not_active')));
            $contract['blockers'][] = 'restore_not_active';
            $contract['blockers'] = array_values(array_unique($contract['blockers']));
        }
        $contract['snapshot'] = array(
            'status' => sanitize_key((string) ($audit['status'] ?? 'review_only')),
            'provider_url_count' => intval($audit['current_provider_inventory']['url_count'] ?? 0),
            'candidate_url_count' => intval($audit['aysa_candidate_inventory']['candidate_url_count'] ?? 0),
            'parity_status' => sanitize_key((string) ($contract['parity']['parity_status'] ?? 'insufficient_data')),
        );
        $contract['lifecycle_gate_summary'] = $this->aysa_technical_sitemap_authority_lifecycle_gate_summary(
            $contract,
            $backup,
            $restore_preview,
            $post_takeover_readback,
            $history_contract,
            $staging_contract,
            $billing_contract
        );
        return $contract;
    }

    private function aysa_technical_load_robots_data($target_url, $primary_sitemap = '') {
        $file_path = $this->aysa_technical_robots_file_path();
        $file_exists = file_exists($file_path);
        $content = '';
        $source = 'virtual-default';
        $source_label = 'WordPress virtual default';

        if ($file_exists) {
            $content = (string) @file_get_contents($file_path);
            $source = 'file';
            $source_label = 'Physical robots.txt';
        } else {
            $lines = array(
                'User-agent: *',
                'Disallow:',
            );
            if ($primary_sitemap !== '') {
                $lines[] = 'Sitemap: ' . $primary_sitemap;
            } else {
                $lines[] = 'Sitemap: ' . untrailingslashit((string) $target_url) . '/wp-sitemap.xml';
            }
            $content = implode("\n", $lines) . "\n";
        }

        $writable = false;
        if ($file_exists) {
            // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- Admin-authorized WordPress-root file operations retain a fail-closed writability preflight before the guarded write.
            $writable = is_writable($file_path);
        } else {
            // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- Admin-authorized WordPress-root file operations retain a fail-closed writability preflight before the guarded write.
            $writable = is_writable(dirname($file_path));
        }

        return array(
            'content' => $content,
            'source' => $source,
            'source_label' => $source_label,
            'path' => $file_path,
            'exists' => $file_exists,
            'writable' => $writable,
            'last_modified' => $file_exists ? @date_i18n('Y-m-d H:i:s', intval(@filemtime($file_path))) : '',
        );
    }

    private function aysa_technical_detect_active_sitemap_providers() {
        $providers = array();
        $active_plugins = (array) get_option('active_plugins', array());
        if (in_array('wordpress-seo/wp-seo.php', $active_plugins, true) || class_exists('WPSEO_Options')) {
            $providers[] = 'Yoast SEO';
        }
        if (in_array('seo-by-rank-math/rank-math.php', $active_plugins, true) || class_exists('RankMath')) {
            $providers[] = 'Rank Math';
        }
        if (in_array('all-in-one-seo-pack/all_in_one_seo_pack.php', $active_plugins, true) || class_exists('AIOSEO\\Plugin\\Common\\Main')) {
            $providers[] = 'All in One SEO';
        }
        $providers[] = 'WordPress Core';
        return array_values(array_unique($providers));
    }

    private function aysa_technical_post_is_noindex($post_id) {
        $post_id = intval($post_id);
        if ($post_id <= 0) {
            return false;
        }
        $robots_meta = get_post_meta($post_id, 'aysa_robots_meta', true);
        if (is_array($robots_meta)) {
            if (!empty($robots_meta['noindex'])) {
                return true;
            }
            if (isset($robots_meta['index']) && strtolower((string) $robots_meta['index']) === 'noindex') {
                return true;
            }
        }
        $yoast_noindex = (string) get_post_meta($post_id, '_yoast_wpseo_meta-robots-noindex', true);
        if ($yoast_noindex === '1' || strtolower($yoast_noindex) === 'noindex') {
            return true;
        }
        $yoast_robots = strtolower((string) get_post_meta($post_id, '_yoast_wpseo_robots', true));
        return ($yoast_robots !== '' && strpos($yoast_robots, 'noindex') !== false);
    }

    private function aysa_technical_term_is_noindex($term_id) {
        $term_id = intval($term_id);
        if ($term_id <= 0) {
            return false;
        }
        $robots_meta = get_term_meta($term_id, 'aysa_robots_meta', true);
        if (is_array($robots_meta)) {
            if (!empty($robots_meta['noindex'])) {
                return true;
            }
            if (isset($robots_meta['index']) && strtolower((string) $robots_meta['index']) === 'noindex') {
                return true;
            }
        }
        $yoast_noindex = (string) get_term_meta($term_id, '_yoast_wpseo_meta-robots-noindex', true);
        return ($yoast_noindex === '1' || strtolower($yoast_noindex) === 'noindex');
    }

    private function aysa_technical_sitemap_url_is_excluded($url) {
        $path = wp_parse_url((string) $url, PHP_URL_PATH);
        $path = strtolower(is_string($path) ? $path : '');
        if ($path === '') {
            return false;
        }
        foreach (array('/cart/', '/checkout/', '/my-account/', '/account/', '/search/') as $blocked) {
            if (strpos($path, $blocked) !== false) {
                return true;
            }
        }
        $query = wp_parse_url((string) $url, PHP_URL_QUERY);
        if (is_string($query) && $query !== '') {
            return true;
        }
        return false;
    }

    private function aysa_technical_sitemap_post_type_is_excluded($post_type) {
        $post_type = sanitize_key((string) $post_type);
        return in_array($post_type, array(
            'attachment',
            'elementor_library',
            'e-landing-page',
            'wp_template',
            'wp_template_part',
            'wp_global_styles',
            'wp_navigation',
            'nav_menu_item',
            'revision',
            'custom_css',
            'customize_changeset',
            'oembed_cache',
            'user_request',
            'shop_order',
            'shop_coupon',
            'shop_webhook',
            'product_variation',
            'cms_block',
            'woodmart_slide',
            'woodmart_slider',
            'woodmart_size_guide',
        ), true);
    }

    private function aysa_technical_sitemap_language_slug_from_code($code) {
        $code = strtolower(trim((string) $code));
        if ($code === '') {
            return '';
        }
        $code = str_replace('_', '-', $code);
        $parts = explode('-', $code);
        return sanitize_title((string) ($parts[0] ?? $code));
    }

    private function aysa_technical_sitemap_multilingual_languages() {
        $languages = array();
        $push = static function(&$languages_ref, $code, $slug, $is_default, $source) {
            $raw_code = trim((string) $code);
            $code = strtolower(str_replace('_', '-', trim((string) $code)));
            $slug = sanitize_title((string) $slug);
            if ($code === '' || $slug === '') {
                return;
            }
            $key = $slug;
            if (!isset($languages_ref[$key])) {
                $languages_ref[$key] = array(
                    'code' => sanitize_key($code),
                    'raw_code' => sanitize_text_field($raw_code),
                    'slug' => $slug,
                    'is_default' => (bool) $is_default,
                    'source' => sanitize_key((string) $source),
                );
            } elseif (!empty($is_default)) {
                $languages_ref[$key]['is_default'] = true;
            }
        };

        $trp_settings = get_option('trp_settings', array());
        if (is_array($trp_settings)) {
            $default_language = strtolower(str_replace('_', '-', (string) ($trp_settings['default-language'] ?? '')));
            $url_slugs = is_array($trp_settings['url-slugs'] ?? null) ? $trp_settings['url-slugs'] : array();
            $translation_languages = is_array($trp_settings['translation-languages'] ?? null) ? $trp_settings['translation-languages'] : array();
            foreach ($translation_languages as $language_code) {
                $language_code = (string) $language_code;
                $normalized = strtolower(str_replace('_', '-', $language_code));
                $slug = '';
                if (isset($url_slugs[$language_code])) {
                    $slug = (string) $url_slugs[$language_code];
                } elseif (isset($url_slugs[$normalized])) {
                    $slug = (string) $url_slugs[$normalized];
                }
                if ($slug === '') {
                    $slug = $this->aysa_technical_sitemap_language_slug_from_code($language_code);
                }
                $push($languages, $language_code, $slug, $normalized !== '' && $normalized === $default_language, 'translatepress');
            }
        }

        if (function_exists('pll_languages_list')) {
            $pll_languages = pll_languages_list(array('fields' => array()));
            if (is_array($pll_languages)) {
                $default_slug = function_exists('pll_default_language') ? (string) pll_default_language('slug') : '';
                foreach ($pll_languages as $language) {
                    if (is_object($language)) {
                        $slug = (string) ($language->slug ?? '');
                        $code = (string) ($language->locale ?? $slug);
                        $push($languages, $code, $slug, $slug !== '' && $slug === $default_slug, 'polylang');
                    } elseif (is_array($language)) {
                        $slug = (string) ($language['slug'] ?? '');
                        $code = (string) ($language['locale'] ?? $slug);
                        $push($languages, $code, $slug, $slug !== '' && $slug === $default_slug, 'polylang');
                    }
                }
            }
        }

        if (has_filter('wpml_active_languages')) {
            // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Intentional interoperability with WPML's documented public filter.
            $wpml_languages = apply_filters('wpml_active_languages', null, array('skip_missing' => 0));
            // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Intentional interoperability with WPML's documented public filter.
            $wpml_default = has_filter('wpml_default_language') ? (string) apply_filters('wpml_default_language', null) : '';
            if (is_array($wpml_languages)) {
                foreach ($wpml_languages as $language) {
                    if (!is_array($language)) {
                        continue;
                    }
                    $code = (string) ($language['language_code'] ?? $language['default_locale'] ?? '');
                    $slug = (string) ($language['code'] ?? $this->aysa_technical_sitemap_language_slug_from_code($code));
                    $push($languages, $code, $slug, $wpml_default !== '' && $slug === $wpml_default, 'wpml');
                }
            }
        }

        return array_values(array_filter($languages, static function($language) {
            return is_array($language) && empty($language['is_default']) && !empty($language['slug']);
        }));
    }

    private function aysa_technical_sitemap_translate_url_for_language($url, array $language) {
        $url = esc_url_raw((string) $url);
        if ($url === '') {
            return '';
        }
        $source = sanitize_key((string) ($language['source'] ?? ''));
        $raw_code = (string) ($language['raw_code'] ?? $language['code'] ?? '');
        $slug = sanitize_title((string) ($language['slug'] ?? ''));

        if ($source === 'translatepress' && class_exists('TRP_Translate_Press')) {
            try {
                $trp = TRP_Translate_Press::get_trp_instance();
                if (is_object($trp) && method_exists($trp, 'get_component')) {
                    $converter = $trp->get_component('url_converter');
                    if (is_object($converter) && method_exists($converter, 'get_url_for_language')) {
                        $translated = $converter->get_url_for_language($raw_code, $url, '');
                        $translated = esc_url_raw((string) $translated);
                        if ($translated !== '' && $this->aysa_technical_sitemap_same_host($url, $translated)) {
                            return $translated;
                        }
                    }
                }
            } catch (Throwable $e) {
                // Keep sitemap candidate generation read-only and resilient if the provider converter is unavailable.
            }
        }

        if ($source === 'wpml' && has_filter('wpml_permalink')) {
            $wpml_code = $slug !== '' ? $slug : $raw_code;
            // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Intentional interoperability with WPML's documented public filter.
            $translated = apply_filters('wpml_permalink', $url, $wpml_code, true);
            $translated = esc_url_raw((string) $translated);
            if ($translated !== '' && $this->aysa_technical_sitemap_same_host($url, $translated)) {
                return $translated;
            }
        }

        return '';
    }

    private function aysa_technical_sitemap_language_url_variants($url, array $languages) {
        $url = esc_url_raw((string) $url);
        if ($url === '' || empty($languages)) {
            return array();
        }
        $scheme = wp_parse_url($url, PHP_URL_SCHEME);
        $host = wp_parse_url($url, PHP_URL_HOST);
        $path = wp_parse_url($url, PHP_URL_PATH);
        $query = wp_parse_url($url, PHP_URL_QUERY);
        if (!is_string($scheme) || $scheme === '' || !is_string($host) || $host === '' || (is_string($query) && $query !== '')) {
            return array();
        }
        $path = '/' . ltrim(is_string($path) && $path !== '' ? $path : '/', '/');
        $variants = array();
        foreach ($languages as $language) {
            $slug = sanitize_title((string) ($language['slug'] ?? ''));
            if ($slug === '') {
                continue;
            }
            $translated_url = $this->aysa_technical_sitemap_translate_url_for_language($url, is_array($language) ? $language : array());
            if ($translated_url !== '' && $translated_url !== $url) {
                $variants[] = $translated_url;
                continue;
            }
            if (preg_match('#^/' . preg_quote($slug, '#') . '(/|$)#i', $path)) {
                continue;
            }
            $variant_path = '/' . $slug . ($path === '/' ? '/' : $path);
            $variants[] = esc_url_raw($scheme . '://' . $host . $variant_path);
        }
        return array_values(array_unique(array_filter($variants)));
    }

    private function aysa_technical_sitemap_push_entry(&$groups, &$seen, $group_key, $group_label, $loc, $lastmod = '') {
        $loc = esc_url_raw((string) $loc);
        if ($loc === '' || isset($seen[$loc]) || $this->aysa_technical_sitemap_url_is_excluded($loc)) {
            return;
        }
        $seen[$loc] = true;
        if (!isset($groups[$group_key]) || !is_array($groups[$group_key])) {
            $groups[$group_key] = array(
                'key' => sanitize_key((string) $group_key),
                'label' => sanitize_text_field((string) $group_label),
                'entries' => array(),
            );
        }
        $row = array('loc' => $loc);
        if (is_string($lastmod) && trim($lastmod) !== '') {
            $row['lastmod'] = trim($lastmod);
        }
        $groups[$group_key]['entries'][] = $row;
    }

    private function aysa_technical_collect_sitemap_groups() {
        $groups = array();
        $seen = array();
        $language_variants = $this->aysa_technical_sitemap_multilingual_languages();

        $home_lastmod = get_lastpostmodified('GMT');
        $this->aysa_technical_sitemap_push_entry($groups, $seen, 'pages', 'Pages', home_url('/'), $home_lastmod ? gmdate('c', strtotime($home_lastmod . ' GMT')) : gmdate('c'));
        foreach ($this->aysa_technical_sitemap_language_url_variants(home_url('/'), $language_variants) as $variant_url) {
            $this->aysa_technical_sitemap_push_entry($groups, $seen, 'pages', 'Pages', $variant_url, $home_lastmod ? gmdate('c', strtotime($home_lastmod . ' GMT')) : gmdate('c'));
        }

        $post_types = get_post_types(array('public' => true), 'objects');
        foreach ($post_types as $post_type => $obj) {
            if (($post_type !== 'page' && empty($obj->publicly_queryable)) || $this->aysa_technical_sitemap_post_type_is_excluded($post_type)) {
                continue;
            }
            $ids = get_posts(array(
                'post_type' => $post_type,
                'post_status' => 'publish',
                'fields' => 'ids',
                'numberposts' => 10000,
                'orderby' => 'modified',
                'order' => 'DESC',
                'suppress_filters' => false,
            ));
            $group_key = $post_type === 'page' ? 'pages' : sanitize_key($post_type);
            $group_label = !empty($obj->labels->name) ? (string) $obj->labels->name : ucfirst((string) $post_type);
            foreach ((array) $ids as $post_id) {
                $post_id = intval($post_id);
                if ($post_id <= 0 || $this->aysa_technical_post_is_noindex($post_id)) {
                    continue;
                }
                $permalink = get_permalink($post_id);
                if (!is_string($permalink) || $permalink === '') {
                    continue;
                }
                $lastmod = (string) get_post_modified_time('c', true, $post_id);
                $this->aysa_technical_sitemap_push_entry($groups, $seen, $group_key, $group_label, $permalink, $lastmod);
                foreach ($this->aysa_technical_sitemap_language_url_variants($permalink, $language_variants) as $variant_url) {
                    $this->aysa_technical_sitemap_push_entry($groups, $seen, $group_key, $group_label, $variant_url, $lastmod);
                }
            }
        }

        $taxonomies = get_taxonomies(array('public' => true), 'objects');
        foreach ($taxonomies as $taxonomy => $obj) {
            if (in_array($taxonomy, array('post_format', 'nav_menu', 'link_category', 'product_shipping_class'), true)) {
                continue;
            }
            $terms = get_terms(array(
                'taxonomy' => $taxonomy,
                'hide_empty' => true,
                'number' => 10000,
            ));
            if (is_wp_error($terms)) {
                continue;
            }
            $group_key = sanitize_key($taxonomy);
            $group_label = !empty($obj->labels->name) ? (string) $obj->labels->name : ucfirst((string) $taxonomy);
            foreach ((array) $terms as $term) {
                if (!($term instanceof WP_Term) || $this->aysa_technical_term_is_noindex($term->term_id)) {
                    continue;
                }
                $term_link = get_term_link($term);
                if (is_wp_error($term_link) || !is_string($term_link) || $term_link === '') {
                    continue;
                }
                $this->aysa_technical_sitemap_push_entry($groups, $seen, $group_key, $group_label, $term_link, '');
                foreach ($this->aysa_technical_sitemap_language_url_variants($term_link, $language_variants) as $variant_url) {
                    $this->aysa_technical_sitemap_push_entry($groups, $seen, $group_key, $group_label, $variant_url, '');
                }
            }
        }

        return array_values(array_filter($groups, static function($group) {
            return is_array($group) && !empty($group['entries']);
        }));
    }

    private function aysa_technical_collect_sitemap_entries() {
        $entries = array();
        foreach ($this->aysa_technical_collect_sitemap_groups() as $group) {
            foreach ((array) ($group['entries'] ?? array()) as $entry) {
                if (is_array($entry)) {
                    $entries[] = $entry;
                }
            }
        }
        return $entries;
    }

    private function aysa_technical_build_sitemap_urlset_xml(array $entries) {
        $lines = array(
            '<?xml version="1.0" encoding="UTF-8"?>',
            '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
        );
        foreach ($entries as $entry) {
            $lines[] = '  <url>';
            $lines[] = '    <loc>' . esc_html((string) ($entry['loc'] ?? '')) . '</loc>';
            if (!empty($entry['lastmod'])) {
                $lines[] = '    <lastmod>' . esc_html((string) $entry['lastmod']) . '</lastmod>';
            }
            $lines[] = '  </url>';
        }
        $lines[] = '</urlset>';

        return array(
            'xml' => implode("\n", $lines) . "\n",
            'entries_count' => count($entries),
        );
    }

    private function aysa_technical_sitemap_child_slug($group_key, $page = 1) {
        $group_key = sanitize_key((string) $group_key);
        $page = max(1, intval($page));
        return $page > 1 ? $group_key . '-sitemap-' . $page . '.xml' : $group_key . '-sitemap.xml';
    }

    private function aysa_technical_build_sitemap_index_xml($target_url = '') {
        $target_url = $target_url !== '' ? untrailingslashit((string) $target_url) : untrailingslashit(home_url('/'));
        $groups = $this->aysa_technical_collect_sitemap_groups();
        $children = array();
        $files = array();
        $entries_count = 0;
        foreach ($groups as $group) {
            $entries = array_values((array) ($group['entries'] ?? array()));
            $chunks = array_chunk($entries, 1000);
            foreach ($chunks as $page => $chunk) {
                $lastmod = '';
                foreach ($chunk as $entry) {
                    if (!empty($entry['lastmod']) && (string) $entry['lastmod'] > $lastmod) {
                        $lastmod = (string) $entry['lastmod'];
                    }
                }
                $slug = $this->aysa_technical_sitemap_child_slug((string) ($group['key'] ?? 'sitemap'), $page + 1);
                $children[] = array(
                    'key' => sanitize_key((string) ($group['key'] ?? 'sitemap')),
                    'label' => sanitize_text_field((string) ($group['label'] ?? 'Sitemap')),
                    'url' => $target_url . '/' . $slug,
                    'entries_count' => count($chunk),
                    'lastmod' => $lastmod !== '' ? $lastmod : gmdate('c'),
                    'page' => $page + 1,
                );
                $urlset = $this->aysa_technical_build_sitemap_urlset_xml($chunk);
                $files[] = array(
                    'slug' => $slug,
                    'xml' => (string) ($urlset['xml'] ?? ''),
                    'entries_count' => count($chunk),
                );
                $entries_count += count($chunk);
            }
        }

        $lines = array(
            '<?xml version="1.0" encoding="UTF-8"?>',
            '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
        );
        foreach ($children as $child) {
            $lines[] = '  <sitemap>';
            $lines[] = '    <loc>' . esc_html((string) ($child['url'] ?? '')) . '</loc>';
            $lines[] = '    <lastmod>' . esc_html((string) ($child['lastmod'] ?? gmdate('c'))) . '</lastmod>';
            $lines[] = '  </sitemap>';
        }
        $lines[] = '</sitemapindex>';

        return array(
            'xml' => implode("\n", $lines) . "\n",
            'entries_count' => $entries_count,
            'children_count' => count($children),
            'children' => $children,
            'files' => $files,
        );
    }

    private function aysa_technical_build_sitemap_xml($target_url = '') {
        return $this->aysa_technical_build_sitemap_index_xml($target_url);
    }

    private function aysa_technical_generate_own_sitemap($target_url = '') {
        $target_url = trim((string) $target_url);
        if ($target_url === '') {
            $target_url = home_url('/');
        }

        $built = $this->aysa_technical_build_sitemap_xml($target_url);
        $file_path = $this->aysa_technical_sitemap_file_path();
        $mode = 'virtual';
        $saved = false;
        $children_saved_count = 0;
        $write_error = '';

        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- Admin-authorized WordPress-root file operations retain a fail-closed writability preflight before the guarded write.
        if (file_exists($file_path) ? is_writable($file_path) : (is_dir(dirname($file_path)) && is_writable(dirname($file_path)))) {
            $token = substr(hash('sha256', wp_generate_uuid4() . '|' . microtime(true)), 0, 16);
            $pending = array();
            $files = is_array($built['files'] ?? null) ? $built['files'] : array();
            foreach ($files as $file) {
                $slug = sanitize_file_name((string) ($file['slug'] ?? ''));
                $xml = (string) ($file['xml'] ?? '');
                if ($slug === '' || !str_ends_with($slug, '.xml') || $xml === '') {
                    $write_error = 'invalid_child_sitemap_payload';
                    break;
                }
                $destination = trailingslashit(dirname($file_path)) . $slug;
                $temporary = $destination . '.aysa-' . $token . '.tmp';
                // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- Sitemap files are generated locally and staged before an atomic publish.
                if (@file_put_contents($temporary, $xml) === false) {
                    $write_error = 'child_sitemap_stage_failed';
                    break;
                }
                $pending[] = array('temporary' => $temporary, 'destination' => $destination, 'child' => true);
            }
            $index_temporary = $file_path . '.aysa-' . $token . '.tmp';
            if ($write_error === '') {
                // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- The index is staged and published only after every child sitemap is ready.
                if (@file_put_contents($index_temporary, (string) ($built['xml'] ?? '')) === false) {
                    $write_error = 'sitemap_index_stage_failed';
                } else {
                    $pending[] = array('temporary' => $index_temporary, 'destination' => $file_path, 'child' => false);
                }
            }
            if ($write_error === '') {
                foreach ($pending as $pending_file) {
                    // phpcs:ignore WordPress.WP.AlternativeFunctions.rename_rename -- Same-directory rename publishes a fully staged sitemap file without exposing a partial write.
                    if (!@rename((string) $pending_file['temporary'], (string) $pending_file['destination'])) {
                        $write_error = 'sitemap_publish_failed';
                        break;
                    }
                    // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- Applies a fixed safe mode only after an administrator-authorized sitemap write.
                    @chmod((string) $pending_file['destination'], 0644);
                    if (!empty($pending_file['child'])) {
                        $children_saved_count++;
                    }
                }
            }
            foreach ($pending as $pending_file) {
                $temporary = (string) ($pending_file['temporary'] ?? '');
                if ($temporary !== '' && file_exists($temporary)) {
                    wp_delete_file($temporary);
                }
            }
            $expected_children = count($files);
            if ($write_error === '' && $expected_children > 0 && $children_saved_count === $expected_children && file_exists($file_path)) {
                $mode = 'physical';
                $saved = true;
            }
        }
        $legacy_file_path = trailingslashit(ABSPATH) . 'sitemap.xml';
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- Admin-authorized WordPress-root file operations retain a fail-closed writability preflight before the guarded write.
        if ($legacy_file_path !== $file_path && file_exists($legacy_file_path) && is_writable($legacy_file_path)) {
            wp_delete_file($legacy_file_path);
        }

        $state = array(
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'url' => $this->aysa_technical_preferred_sitemap_url($target_url),
            'entries_count' => intval($built['entries_count'] ?? 0),
            'children_count' => intval($built['children_count'] ?? 0),
            'children' => is_array($built['children'] ?? null) ? $built['children'] : array(),
            'children_saved_count' => $children_saved_count,
            'write_error' => $write_error,
            'mode' => $mode,
            'saved' => $saved,
        );
        update_option($this->aysa_technical_sitemap_generation_state_key(), $state, false);
        return $state;
    }

    public function maybe_serve_aysa_technical_sitemap() {
        if (!$this->aysa_technical_sitemap_authority_runtime_active(0, home_url('/'))) {
            return;
        }
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $request_uri = isset($_SERVER['REQUEST_URI']) ? (string) wp_unslash($_SERVER['REQUEST_URI']) : '';
        if ($request_uri === '') {
            return;
        }
        $path = wp_parse_url($request_uri, PHP_URL_PATH);
        $path = is_string($path) ? untrailingslashit($path) : '';
        if ($path === '') {
            $path = '/';
        }
        if ($path === '/sitemap.xml' || $path === '/wp-sitemap.xml') {
            wp_safe_redirect($this->aysa_technical_preferred_sitemap_url(home_url('/')), 301);
            exit;
        }

        if ($path === '/sitemap_index.xml') {
            $built = $this->aysa_technical_build_sitemap_index_xml(home_url('/'));
            status_header(200);
            nocache_headers();
            header('Content-Type: application/xml; charset=UTF-8');
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- XML is assembled exclusively by the local sitemap builder, which escapes every dynamic node.
            echo (string) ($built['xml'] ?? '');
            exit;
        }

        if (preg_match('#^/([a-z0-9_-]+)-sitemap(?:-([0-9]+))?\.xml$#', $path, $matches)) {
            $group_key = sanitize_key((string) ($matches[1] ?? ''));
            $page = max(1, intval($matches[2] ?? 1));
            foreach ($this->aysa_technical_collect_sitemap_groups() as $group) {
                if (sanitize_key((string) ($group['key'] ?? '')) !== $group_key) {
                    continue;
                }
                $chunks = array_chunk(array_values((array) ($group['entries'] ?? array())), 1000);
                $entries = isset($chunks[$page - 1]) && is_array($chunks[$page - 1]) ? $chunks[$page - 1] : array();
                if ($entries === array()) {
                    break;
                }
                $built = $this->aysa_technical_build_sitemap_urlset_xml($entries);
                status_header(200);
                nocache_headers();
                header('Content-Type: application/xml; charset=UTF-8');
                // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- XML is assembled exclusively by the local sitemap builder, which escapes every dynamic node.
                echo (string) ($built['xml'] ?? '');
                exit;
            }
        }
    }

    public function filter_aysa_technical_robots_txt($output, $public) {
        if (!$this->aysa_technical_sitemap_authority_runtime_active(0, home_url('/'))) {
            return $output;
        }
        $content = (string) $output;
        $sitemap_url = $this->aysa_technical_preferred_sitemap_url(home_url('/'));
        if (trim($content) === '') {
            return $this->aysa_technical_robots_default_content(home_url('/'), $sitemap_url);
        }
        if (stripos($content, 'Sitemap:') === false) {
            $content = rtrim($content) . "\nSitemap: " . $sitemap_url . "\n";
        }
        return $content;
    }

    public function filter_aysa_technical_sitemap_provider_enabled($enabled) {
        if (!$this->aysa_technical_sitemap_authority_runtime_active(0, home_url('/'))) {
            return $enabled;
        }
        return false;
    }

    private function aysa_technical_robots_default_content($target_url, $primary_sitemap = '') {
        $sitemap_url = $primary_sitemap !== '' ? $primary_sitemap : $this->aysa_technical_preferred_sitemap_url($target_url);
        $lines = array(
            'User-agent: *',
            'Disallow: /wp-admin/',
            'Allow: /wp-admin/admin-ajax.php',
            'Disallow: /cart/',
            'Disallow: /checkout/',
            'Disallow: /my-account/',
            'Disallow: /?add-to-cart=',
            '',
            'Sitemap: ' . $sitemap_url,
        );
        return implode("\n", $lines) . "\n";
    }

    private function aysa_technical_save_robots_content($content) {
        $file_path = $this->aysa_technical_robots_file_path();
        $content = (string) $content;
        $content = str_replace("\r\n", "\n", $content);
        $content = str_replace("\r", "\n", $content);
        $content = str_replace("\0", '', $content);
        $lines = explode("\n", $content);
        $clean_lines = array();
        foreach ($lines as $line) {
            $clean_lines[] = rtrim((string) $line, " \t");
        }
        $normalized = trim(implode("\n", $clean_lines)) . "\n";
        if ($normalized === "\n") {
            return new WP_Error('robots_empty', 'robots.txt content cannot be empty.');
        }
        if (strlen($normalized) > 120000) {
            return new WP_Error('robots_too_large', 'robots.txt content is too large.');
        }

        if (file_exists($file_path)) {
            // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- Admin-authorized WordPress-root file operations retain a fail-closed writability preflight before the guarded write.
            if (!is_writable($file_path)) {
                return new WP_Error('robots_not_writable', 'robots.txt is not writable.');
            }
        } else {
            $dir = dirname($file_path);
            // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- Admin-authorized WordPress-root file operations retain a fail-closed writability preflight before the guarded write.
            if (!is_dir($dir) || !is_writable($dir)) {
                return new WP_Error('robots_not_writable', 'Cannot create robots.txt in WordPress root (directory not writable).');
            }
        }

        $bytes = @file_put_contents($file_path, $normalized);
        if ($bytes === false) {
            return new WP_Error('robots_write_failed', 'Failed to write robots.txt.');
        }
        return true;
    }

    private function aysa_technical_build_sitemap_robots_audit($target_url, $project_id = 0) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
        }
        $latest_takeover_for_fast_path = $this->aysa_technical_sitemap_authority_latest_takeover_history($project_id, $target_url);
        if ($latest_takeover_for_fast_path !== array()) {
            $preferred_sitemap = $this->aysa_technical_preferred_sitemap_url($target_url);
            $generation = get_option($this->aysa_technical_sitemap_generation_state_key(), array());
            if (!is_array($generation)) {
                $generation = array();
            }
            $backup_id = sanitize_text_field((string) ($latest_takeover_for_fast_path['backup_id'] ?? ''));
            $backup = $backup_id !== '' ? $this->aysa_technical_sitemap_authority_backup_by_id($project_id, $backup_id) : array();
            $robots = $this->aysa_technical_load_robots_data($target_url, $preferred_sitemap);
            $readback = is_array($latest_takeover_for_fast_path['readback_result'] ?? null)
                ? $latest_takeover_for_fast_path['readback_result']
                : array();
            if ($readback === array()) {
                $readback = array(
                    'operation' => 'technical_sitemap_authority_post_takeover_readback',
                    'status' => sanitize_key((string) ($latest_takeover_for_fast_path['status'] ?? 'completed_with_warnings')),
                    'project_id' => $project_id,
                    'target_url' => $target_url,
                    'backup_id' => $backup_id,
                    'dry_run' => false,
                    'active' => true,
                    'selectable' => false,
                    'readback_available' => true,
                    'verified' => (string) ($latest_takeover_for_fast_path['status'] ?? '') === 'verified',
                    'verified_change_count' => intval($latest_takeover_for_fast_path['verified_change_count'] ?? 0),
                    'writes_enabled' => false,
                    'billing_enabled' => false,
                    'billing_status' => 'disabled_for_rollout',
                    'live_readback' => array(
                        'future_sitemap_index_url' => $preferred_sitemap,
                        'live_url_count' => intval($latest_takeover_for_fast_path['apply_result']['entries_count'] ?? $generation['entries_count'] ?? 0),
                        'live_child_sitemap_count' => intval($latest_takeover_for_fast_path['apply_result']['children_count'] ?? $generation['children_count'] ?? 0),
                        'live_url_list_hash' => sanitize_text_field((string) ($latest_takeover_for_fast_path['candidate_inventory_hash'] ?? '')),
                        'live_errors' => array(),
                        'robots_current_hash' => hash('sha256', (string) ($robots['content'] ?? '')),
                    ),
                    'blockers' => array('billing_gate_off'),
                );
            }
            $provider_count = intval($readback['provider_url_count'] ?? $backup['current_provider_url_count'] ?? 0);
            $candidate_count = intval($readback['candidate_url_count'] ?? $readback['live_readback']['live_url_count'] ?? $latest_takeover_for_fast_path['apply_result']['entries_count'] ?? $generation['entries_count'] ?? 0);
            $active_takeover = array(
                'operation' => 'technical_sitemap_authority_apply_bridge',
                'status' => (string) ($latest_takeover_for_fast_path['status'] ?? ($readback['status'] ?? 'completed_with_warnings')),
                'active' => true,
                'history_id' => sanitize_text_field((string) ($latest_takeover_for_fast_path['history_id'] ?? '')),
                'backup_id' => $backup_id,
                'approved_at' => sanitize_text_field((string) ($latest_takeover_for_fast_path['approved_at'] ?? '')),
                'future_provider_after' => sanitize_text_field((string) ($latest_takeover_for_fast_path['future_provider_after'] ?? 'AYSA')),
                'restore_available' => !empty($latest_takeover_for_fast_path['restore_available']) || !empty($backup['restore_available']) || !empty($backup['robots_before_content']),
                'restore_status' => sanitize_text_field((string) ($latest_takeover_for_fast_path['restore_status'] ?? ($backup['restore_status'] ?? 'restore_available'))),
                'verified' => !empty($readback['verified']) || (string) ($latest_takeover_for_fast_path['status'] ?? '') === 'verified',
                'verified_change_count' => intval($readback['verified_change_count'] ?? $latest_takeover_for_fast_path['verified_change_count'] ?? 0),
                'billing_enabled' => false,
                'billing_status' => 'disabled_for_rollout',
                'readback' => $readback,
                'history_row' => $latest_takeover_for_fast_path,
            );
            $dry_run = array(
                'operation' => 'technical_sitemap_authority_takeover_dry_run',
                'status' => !empty($active_takeover['verified']) ? 'verified_active_takeover' : 'active_takeover_completed_with_warnings',
                'active' => true,
                'dry_run' => false,
                'selectable' => false,
                'apply_execution_available' => false,
                'writes_enabled' => false,
                'billing_enabled' => false,
                'billing_status' => 'disabled_for_rollout',
                'provider_changed' => true,
                'robots_changed' => true,
                'sitemap_written' => true,
                'gsc_submitted' => false,
                'active_takeover' => $active_takeover,
                'post_takeover_readback' => $readback,
                'takeover_history_contract' => array(
                    'operation' => 'technical_sitemap_authority_takeover_history_contract',
                    'status' => 'history_row_available',
                    'active' => true,
                    'history_execution_available' => true,
                    'history_row_available' => true,
                    'restore_reference_available' => !empty($active_takeover['restore_available']),
                    'backup_id' => $backup_id,
                    'billing_enabled' => false,
                    'billing_status' => 'disabled_for_rollout',
                    'blockers' => array('billing_gate_off'),
                ),
                'apply_bridge_contract' => array(
                    'operation' => 'technical_sitemap_authority_apply_bridge',
                    'status' => !empty($active_takeover['verified']) ? 'verified_active_takeover' : 'active_takeover_completed_with_warnings',
                    'backup_id' => $backup_id,
                    'dry_run' => false,
                    'active' => true,
                    'selectable' => false,
                    'apply_execution_available' => false,
                    'restore_execution_available' => !empty($active_takeover['restore_available']),
                    'writes_enabled' => false,
                    'billing_enabled' => false,
                    'billing_status' => 'disabled_for_rollout',
                    'provider_changed' => true,
                    'robots_changed' => true,
                    'sitemap_written' => true,
                    'gsc_submitted' => false,
                    'blockers' => array('already_active', 'billing_gate_off'),
                ),
            );
            $checks = array(
                array(
                    'id' => 'sitemap_authority_active_takeover',
                    'label' => 'AYSA sitemap authority takeover is active',
                    'status' => !empty($active_takeover['verified']) ? 'pass' : 'warning',
                    'message' => !empty($active_takeover['verified']) ? 'Takeover is recorded and verified by readback.' : 'Takeover is recorded; readback finished with warnings.',
                ),
                array(
                    'id' => 'restore_available',
                    'label' => 'Restore reference is available',
                    'status' => !empty($active_takeover['restore_available']) ? 'pass' : 'warning',
                    'message' => !empty($active_takeover['restore_available']) ? 'Restore can be prepared from the takeover backup.' : 'Restore backup content is not available.',
                ),
                array(
                    'id' => 'billing_disabled',
                    'label' => 'Billing remains disabled',
                    'status' => 'pass',
                    'message' => 'Sitemap Authority billing is disabled for rollout.',
                ),
            );
            return array(
                'generated_at' => current_time('mysql'),
                'target_url' => $target_url,
                'primary_sitemap' => $preferred_sitemap,
                'robots' => $robots,
                'sitemaps' => array(
                    array(
                        'provider' => 'aysa',
                        'provider_label' => 'AYSA',
                        'url' => $preferred_sitemap,
                        'reachable' => true,
                        'type' => 'active_takeover',
                    ),
                ),
                'checks' => $checks,
                'current_provider_inventory' => array(
                    'status' => 'active_takeover_history',
                    'url_count' => $provider_count,
                ),
                'aysa_candidate_inventory' => array(
                    'status' => 'active_takeover_history',
                    'candidate_url_count' => $candidate_count,
                    'candidate_inventory_hash' => sanitize_text_field((string) ($latest_takeover_for_fast_path['candidate_inventory_hash'] ?? $readback['live_readback']['live_url_list_hash'] ?? '')),
                ),
                'sitemap_parity_diff' => array(
                    'parity_status' => sanitize_key((string) ($readback['parity_status'] ?? 'active_takeover_verified')),
                    'status' => sanitize_key((string) ($readback['parity_status'] ?? 'active_takeover_verified')),
                    'missing_from_candidate_count' => 0,
                    'extra_in_candidate_count' => 0,
                ),
                'sitemap_authority_dry_run_bridge' => $dry_run,
                'sitemap_authority_active_takeover' => $active_takeover,
                'generation' => array(
                    'mode' => sanitize_text_field((string) ($generation['mode'] ?? ($latest_takeover_for_fast_path['apply_result']['generation_mode'] ?? 'active_takeover'))),
                    'entries_count' => $candidate_count,
                    'children_count' => intval($generation['children_count'] ?? $latest_takeover_for_fast_path['apply_result']['children_count'] ?? 0),
                    'children' => is_array($generation['children'] ?? null) ? $generation['children'] : array(),
                    'updated_at' => sanitize_text_field((string) ($generation['updated_at'] ?? $latest_takeover_for_fast_path['created_at'] ?? '')),
                    'url' => esc_url_raw((string) ($generation['url'] ?? $preferred_sitemap)),
                    'saved' => true,
                ),
                'providers' => array('AYSA'),
                'summary' => array(
                    'errors' => 0,
                    'warnings' => !empty($active_takeover['verified']) ? 0 : 1,
                    'passes' => !empty($active_takeover['restore_available']) ? 3 : 2,
                    'robots_health' => !empty($active_takeover['verified']) ? '100/100' : '67/100',
                    'sitemap_health' => '100/100',
                    'wordpress_visibility' => ((string) get_option('blog_public', '1') !== '0') ? 'enabled' : 'blocked',
                    'sitemap_mode' => sanitize_text_field((string) ($generation['mode'] ?? 'active_takeover')),
                    'providers_label' => 'AYSA',
                    'legacy_conflicts' => 0,
                    'sitemap_entries' => $candidate_count,
                    'current_provider_sitemap_entries' => $provider_count,
                    'aysa_candidate_sitemap_entries' => $candidate_count,
                    'legacy_redirected' => 0,
                    'reachable_sitemaps' => 1,
                ),
            );
        }
        $sitemaps = $this->aysa_technical_fetch_sitemap_candidates($target_url);
        $generation = get_option($this->aysa_technical_sitemap_generation_state_key(), array());
        if (!is_array($generation)) {
            $generation = array();
        }
        $primary_sitemap = '';
        foreach ($sitemaps as $row) {
            if (!empty($row['reachable']) && !empty($row['url']) && (string) ($row['provider'] ?? '') === 'aysa') {
                $primary_sitemap = (string) $row['url'];
                break;
            }
        }
        if ($primary_sitemap === '') {
            foreach ($sitemaps as $row) {
                if (!empty($row['reachable']) && !empty($row['url'])) {
                    $primary_sitemap = (string) $row['url'];
                    break;
                }
            }
        }
        if ($primary_sitemap === '') {
            $primary_sitemap = $this->aysa_technical_preferred_sitemap_url($target_url);
        }

        $robots = $this->aysa_technical_load_robots_data($target_url, $primary_sitemap);
        $robots_content = (string) ($robots['content'] ?? '');
        $normalized = strtolower($robots_content);
        $wordpress_visibility_enabled = ((string) get_option('blog_public', '1') !== '0');
        $providers = $this->aysa_technical_detect_active_sitemap_providers();
        $preferred_sitemap = $this->aysa_technical_preferred_sitemap_url($target_url);
        $current_provider_urls = array();
        $aysa_candidate_urls = array();
        $current_provider_inventory = $this->aysa_technical_build_current_provider_sitemap_inventory($target_url, $primary_sitemap, $providers, $current_provider_urls);
        $public_child_count = intval($current_provider_inventory['child_sitemap_count'] ?? 0);
        $unreachable_public_child_count = intval($current_provider_inventory['unreachable_child_sitemap_count'] ?? 0);
        $invalid_public_child_count = intval($current_provider_inventory['invalid_child_sitemap_count'] ?? 0);
        $public_children_healthy = !empty($current_provider_inventory['children_healthy']);
        $aysa_candidate_inventory = $this->aysa_technical_build_aysa_candidate_sitemap_inventory($target_url, $aysa_candidate_urls);
        $sitemap_parity_diff = $this->aysa_technical_build_sitemap_parity_diff($current_provider_urls, $aysa_candidate_urls, $target_url);
        $sitemap_authority_dry_run = $this->aysa_technical_build_sitemap_authority_dry_run_contract($project_id, $target_url, array(
            'robots' => $robots,
            'primary_sitemap' => $primary_sitemap,
            'providers' => $providers,
            'current_provider_inventory' => $current_provider_inventory,
            'aysa_candidate_inventory' => $aysa_candidate_inventory,
            'sitemap_parity_diff' => $sitemap_parity_diff,
        ));
        $latest_takeover = $this->aysa_technical_sitemap_authority_latest_takeover_history($project_id, $target_url);
        $active_takeover = array();
        if ($latest_takeover !== array()) {
            $backup_id = sanitize_text_field((string) ($latest_takeover['backup_id'] ?? ''));
            $backup = $backup_id !== '' ? $this->aysa_technical_sitemap_authority_backup_by_id($project_id, $backup_id) : array();
            $restore_preview = $this->aysa_technical_sitemap_authority_restore_preview($project_id, $backup, array(
                'robots' => $robots,
                'primary_sitemap' => $primary_sitemap,
                'providers' => $providers,
                'current_provider_inventory' => $current_provider_inventory,
                'aysa_candidate_inventory' => $aysa_candidate_inventory,
                'sitemap_parity_diff' => $sitemap_parity_diff,
            ));
            $readback_contract = $this->aysa_technical_sitemap_authority_post_takeover_readback_contract($backup, $restore_preview, array(
                'robots' => $robots,
                'primary_sitemap' => $primary_sitemap,
                'providers' => $providers,
                'current_provider_inventory' => $current_provider_inventory,
                'aysa_candidate_inventory' => $aysa_candidate_inventory,
                'sitemap_parity_diff' => $sitemap_parity_diff,
                'sitemaps' => $sitemaps,
            ));
            $readback = $this->aysa_technical_sitemap_authority_evaluate_post_takeover_readback(
                $project_id,
                $target_url,
                $backup,
                $restore_preview,
                array(
                    'robots' => $robots,
                    'primary_sitemap' => $primary_sitemap,
                    'providers' => $providers,
                    'current_provider_inventory' => $current_provider_inventory,
                    'aysa_candidate_inventory' => $aysa_candidate_inventory,
                    'sitemap_parity_diff' => $sitemap_parity_diff,
                    'sitemaps' => $sitemaps,
                ),
                $readback_contract,
                true
            );
            $active_takeover = array(
                'operation' => 'technical_sitemap_authority_apply_bridge',
                'status' => (string) ($latest_takeover['status'] ?? ($readback['status'] ?? 'completed_with_warnings')),
                'active' => true,
                'history_id' => sanitize_text_field((string) ($latest_takeover['history_id'] ?? '')),
                'backup_id' => $backup_id,
                'approved_at' => sanitize_text_field((string) ($latest_takeover['approved_at'] ?? '')),
                'future_provider_after' => sanitize_text_field((string) ($latest_takeover['future_provider_after'] ?? 'AYSA')),
                'restore_available' => !empty($latest_takeover['restore_available']) || !empty($backup['restore_available']),
                'restore_status' => sanitize_text_field((string) ($latest_takeover['restore_status'] ?? ($backup['restore_status'] ?? 'restore_available'))),
                'verified' => !empty($readback['verified']) || (string) ($latest_takeover['status'] ?? '') === 'verified',
                'verified_change_count' => intval($readback['verified_change_count'] ?? $latest_takeover['verified_change_count'] ?? 0),
                'billing_enabled' => false,
                'billing_status' => 'disabled_for_rollout',
                'readback' => $readback,
                'history_row' => $latest_takeover,
            );
            $sitemap_authority_dry_run['active_takeover'] = $active_takeover;
            $sitemap_authority_dry_run['post_takeover_readback'] = $readback;
            $sitemap_authority_dry_run['takeover_history_contract'] = array(
                'operation' => 'technical_sitemap_authority_takeover_history_contract',
                'status' => 'history_row_available',
                'active' => true,
                'history_execution_available' => true,
                'history_row_available' => true,
                'restore_reference_available' => !empty($active_takeover['restore_available']),
                'backup_id' => $backup_id,
                'billing_enabled' => false,
                'billing_status' => 'disabled_for_rollout',
                'blockers' => array('billing_gate_off'),
            );
            $sitemap_authority_dry_run['apply_bridge_contract'] = array(
                'operation' => 'technical_sitemap_authority_apply_bridge',
                'status' => !empty($active_takeover['verified']) ? 'verified_active_takeover' : 'active_takeover_completed_with_warnings',
                'backup_id' => $backup_id,
                'dry_run' => false,
                'active' => true,
                'selectable' => false,
                'apply_execution_available' => false,
                'restore_execution_available' => !empty($active_takeover['restore_available']),
                'writes_enabled' => false,
                'billing_enabled' => false,
                'billing_status' => 'disabled_for_rollout',
                'provider_changed' => true,
                'robots_changed' => true,
                'sitemap_written' => true,
                'gsc_submitted' => false,
                'blockers' => array('already_active', 'billing_gate_off'),
            );
        }
        $generation_mode = sanitize_text_field((string) ($generation['mode'] ?? 'not_generated'));
        $generation_entries = max(0, intval($generation['entries_count'] ?? 0));
        $generation_saved = !empty($generation['saved']);
        $legacy_conflicts = array();
        $legacy_redirected = array();

        $checks = array();
        $checks[] = array(
            'id' => 'wp_visibility',
            'label' => 'WordPress search visibility is enabled',
            'status' => $wordpress_visibility_enabled ? 'pass' : 'error',
            'message' => $wordpress_visibility_enabled ? 'WordPress is not discouraging search engines.' : 'WordPress currently discourages search engines from indexing the site.',
        );
        $has_user_agent_star = (bool) preg_match('/^\s*user-agent:\s*\*\s*$/im', $robots_content);
        $checks[] = array(
            'id' => 'ua_star',
            'label' => 'Has User-agent: *',
            'status' => $has_user_agent_star ? 'pass' : 'error',
            'message' => $has_user_agent_star ? 'Global crawler directive is present.' : 'Add `User-agent: *` to apply rules for major crawlers.',
        );

        $has_disallow_all = (bool) preg_match('/^\s*disallow:\s*\/\s*$/im', $robots_content);
        $checks[] = array(
            'id' => 'disallow_all',
            'label' => 'No global Disallow /',
            'status' => $has_disallow_all ? 'error' : 'pass',
            'message' => $has_disallow_all ? 'Global `Disallow: /` blocks the whole site from crawling.' : 'No full-site crawl blocking rule detected.',
        );

        $has_sitemap = (bool) preg_match('/^\s*sitemap:\s*https?:\/\/\S+\s*$/im', $robots_content);
        $sitemap_matches_primary = stripos($normalized, strtolower('Sitemap: ' . $preferred_sitemap)) !== false;
        $checks[] = array(
            'id' => 'sitemap_directive',
            'label' => 'robots.txt points to AYSA sitemap',
            'status' => $has_sitemap ? ($sitemap_matches_primary ? 'pass' : 'warning') : 'warning',
            'message' => !$has_sitemap
                ? 'Add a `Sitemap:` directive in robots.txt.'
                : ($sitemap_matches_primary ? 'robots.txt references the AYSA sitemap.' : 'Sitemap directive found, but it does not point to the AYSA sitemap URL.'),
        );

        $danger_patterns = array('/product/', '/category/', '/blog/');
        $danger_hits = array();
        foreach ($danger_patterns as $pattern) {
            if (stripos($normalized, 'disallow: ' . strtolower($pattern)) !== false) {
                $danger_hits[] = $pattern;
            }
        }
        $checks[] = array(
            'id' => 'critical_sections',
            'label' => 'Critical sections are crawlable',
            'status' => empty($danger_hits) ? 'pass' : 'warning',
            'message' => empty($danger_hits)
                ? 'No broad disallow rules detected for key SEO sections.'
                : ('Potentially restrictive rules found for: ' . implode(', ', $danger_hits)),
        );

        $reachable_sitemaps = 0;
        $aysa_reachable = false;
        foreach ($sitemaps as $sitemap_row) {
            if (!empty($sitemap_row['reachable'])) {
                $reachable_sitemaps++;
            }
            if ((string) ($sitemap_row['provider'] ?? '') === 'aysa' && !empty($sitemap_row['reachable'])) {
                $aysa_reachable = true;
            }
            if ((string) ($sitemap_row['provider'] ?? '') !== 'aysa') {
                $legacy_label = (string) ($sitemap_row['provider_label'] ?? $sitemap_row['url']);
                $is_redirect = ((string) ($sitemap_row['type'] ?? '') === 'redirect');
                $redirect_location = untrailingslashit((string) ($sitemap_row['location'] ?? ''));
                if ($is_redirect && $redirect_location === untrailingslashit($preferred_sitemap)) {
                    $legacy_redirected[] = $legacy_label;
                } elseif (!empty($sitemap_row['reachable']) || $is_redirect) {
                    $legacy_conflicts[] = $legacy_label;
                }
            }
        }
        $checks[] = array(
            'id' => 'aysa_sitemap_reachable',
            'label' => 'AYSA sitemap endpoint is reachable',
            'status' => ($aysa_reachable && $public_children_healthy) ? 'pass' : 'error',
            'message' => !$aysa_reachable
                ? 'AYSA sitemap index is not reachable yet. Generate it and verify that /sitemap_index.xml returns valid XML.'
                : (!$public_children_healthy
                    ? ('The sitemap index responds, but ' . ($unreachable_public_child_count + $invalid_public_child_count) . ' of ' . $public_child_count . ' child sitemaps are unreachable or invalid.')
                    : 'AYSA sitemap index and its child sitemaps are reachable.'),
        );
        $checks[] = array(
            'id' => 'sitemap_children_reachable',
            'label' => 'All child sitemaps are reachable and valid',
            'status' => $public_children_healthy ? 'pass' : 'error',
            'message' => $public_children_healthy
                ? ($public_child_count > 0 ? ('Verified ' . $public_child_count . ' child sitemaps.') : 'The public sitemap is a direct URL set or has no child sitemap references.')
                : ('Broken child sitemaps: ' . $unreachable_public_child_count . ' unreachable and ' . $invalid_public_child_count . ' invalid. The sitemap must not be marked healthy.'),
        );
        $checks[] = array(
            'id' => 'legacy_sitemaps',
            'label' => 'Other sitemap endpoints are not competing',
            'status' => empty($legacy_conflicts) ? 'pass' : 'warning',
            'message' => empty($legacy_conflicts)
                ? (empty($legacy_redirected) ? 'No competing sitemap endpoint is active.' : 'Compatibility sitemap endpoints redirect safely to AYSA sitemap.')
                : 'One or more sitemap endpoints still respond directly and should be redirected to AYSA sitemap.',
        );
        $checks[] = array(
            'id' => 'sitemap_generation',
            'label' => 'AYSA sitemap is generated from WordPress content',
            'status' => ($generation_entries > 0 && $aysa_reachable && $public_children_healthy) ? 'pass' : 'warning',
            'message' => ($generation_entries > 0)
                ? ('AYSA sitemap contains ' . $generation_entries . ' entries and is served in `' . $generation_mode . '` mode' . ($generation_saved ? '.' : ' (virtual fallback).'))
                : 'AYSA sitemap has not been generated yet. Use `Generate AYSA Sitemap` first.',
        );
        $checks[] = array(
            'id' => 'robots_writable',
            'label' => 'robots.txt is editable',
            'status' => !empty($robots['writable']) ? 'pass' : 'warning',
            'message' => !empty($robots['writable']) ? 'WordPress can update robots.txt.' : 'robots.txt is not writable from WordPress root.',
        );
        $checks[] = array(
            'id' => 'seo_plugin_sitemaps_disabled',
            'label' => 'Conflicting sitemap endpoints are controlled',
            'status' => empty($legacy_conflicts) ? 'pass' : 'warning',
            'message' => empty($legacy_conflicts)
                ? 'Other sitemap endpoints are not competing with AYSA sitemap.'
                : 'One or more sitemap endpoints still respond directly. Keep only AYSA sitemap active.',
        );

        $error_count = 0;
        $warning_count = 0;
        $pass_count = 0;
        foreach ($checks as $check) {
            $status = (string) ($check['status'] ?? '');
            if ($status === 'error') {
                $error_count++;
            } elseif ($status === 'warning') {
                $warning_count++;
            } elseif ($status === 'pass') {
                $pass_count++;
            }
        }

        $total = max(1, count($checks));
        $robots_health = intval(round(($pass_count / $total) * 100));
        $sitemap_health = intval(round((((($aysa_reachable && $public_children_healthy) ? 1 : 0) + (empty($legacy_conflicts) ? 1 : 0) + ($generation_entries > 0 ? 1 : 0)) / 3) * 100));

        return array(
            'generated_at' => current_time('mysql'),
            'target_url' => $target_url,
            'primary_sitemap' => $primary_sitemap,
            'robots' => $robots,
            'sitemaps' => $sitemaps,
            'checks' => $checks,
            'current_provider_inventory' => $current_provider_inventory,
            'aysa_candidate_inventory' => $aysa_candidate_inventory,
            'sitemap_parity_diff' => $sitemap_parity_diff,
            'sitemap_authority_dry_run_bridge' => $sitemap_authority_dry_run,
            'sitemap_authority_active_takeover' => $active_takeover,
            'generation' => array(
                'mode' => $generation_mode,
                'entries_count' => $generation_entries,
                'children_count' => intval($generation['children_count'] ?? 0),
                'children' => is_array($generation['children'] ?? null) ? $generation['children'] : array(),
                'updated_at' => sanitize_text_field((string) ($generation['updated_at'] ?? '')),
                'url' => esc_url_raw((string) ($generation['url'] ?? $preferred_sitemap)),
                'saved' => $generation_saved,
            ),
            'providers' => $providers,
            'summary' => array(
                'errors' => $error_count,
                'warnings' => $warning_count,
                'passes' => $pass_count,
                'robots_health' => $robots_health . '/100',
                'sitemap_health' => $sitemap_health . '/100',
                'wordpress_visibility' => $wordpress_visibility_enabled ? 'enabled' : 'blocked',
                'sitemap_mode' => $generation_mode,
                'providers_label' => implode(', ', $providers),
                'legacy_conflicts' => count($legacy_conflicts),
                'sitemap_entries' => $generation_entries,
                'current_provider_sitemap_entries' => intval($current_provider_inventory['url_count'] ?? 0),
                'aysa_candidate_sitemap_entries' => intval($aysa_candidate_inventory['candidate_url_count'] ?? 0),
                'legacy_redirected' => count($legacy_redirected),
                'reachable_sitemaps' => $reachable_sitemaps,
                'child_sitemaps' => $public_child_count,
                'unreachable_child_sitemaps' => $unreachable_public_child_count,
                'invalid_child_sitemaps' => $invalid_public_child_count,
                'sitemap_children_healthy' => $public_children_healthy,
            ),
        );
    }

    private function aysa_resolve_project_id_with_local_fallback($project_id = 0) {
        $requested_project_id = intval($project_id);
        $bound_project_id = intval(get_option('aysa_project_id', 0));

        if ($bound_project_id <= 0) {
            $bound_project_id = intval(get_option('aysa_current_project_id', 0));
        }

        if ($bound_project_id <= 0 && !class_exists('Aysa_Wallet_Manager')) {
            $wallet_file = AYSA_PLUGIN_DIR . 'includes/class-aysa-wallet-manager.php';
            if (file_exists($wallet_file)) {
                require_once $wallet_file;
            }
        }
        if ($bound_project_id <= 0 && class_exists('Aysa_Wallet_Manager')) {
            $bound_project_id = intval(Aysa_Wallet_Manager::get_instance()->get_project_id());
        }

        if ($bound_project_id <= 0) {
            return 0;
        }

        if ($requested_project_id > 0 && $requested_project_id !== $bound_project_id) {
            return 0;
        }

        return $bound_project_id;
    }

    private function aysa_verify_technical_audit_nonce() {
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : \x27\x27;
        if ($nonce === '') {
            $nonce = isset($_POST['_ajax_nonce']) ? sanitize_text_field(wp_unslash($_POST['_ajax_nonce'])) : \x27\x27;
        }
        if ($nonce === '') {
            return false;
        }

        $allowed_actions = array(
            'aysa_nonce',
            'aysa_metabox_nonce',
            'aysa-ajax-nonce',
            'aysa_internal_linking_nonce',
            'aysa_schema_nonce',
            'aysa_technical_audit_nonce',
            'aysa_content_strategy_nonce',
        );
        foreach ($allowed_actions as $action) {
            if (wp_verify_nonce($nonce, $action)) {
                return true;
            }
        }
        return false;
    }

    private function aysa_technical_resolve_target_url($project_id = 0) {
        $project_id = $this->aysa_resolve_technical_project_id(intval($project_id));
        if ($project_id > 0) {
            $project_general = get_option('aysa_wizard_general_project_' . $project_id, array());
            if (is_array($project_general)) {
                $website = $this->aysa_setup_normalize_url((string) ($project_general['website'] ?? ''));
                if ($website !== '') {
                    return rtrim($website, '/');
                }
            }
        }

        $site_url = (string) site_url('/');
        return rtrim($site_url, '/');
    }

    private function aysa_wallet_track_server_usage($project_id, $endpoint, $cost_cents, $reference = '', $meta = array()) {
        $project_id = intval($project_id);
        $cost_cents = intval($cost_cents);
        if ($project_id <= 0 || $cost_cents <= 0) {
            return;
        }

        if (!class_exists('Aysa_Wallet_Manager')) {
            $wallet_file = AYSA_PLUGIN_DIR . 'includes/class-aysa-wallet-manager.php';
            if (file_exists($wallet_file)) {
                require_once $wallet_file;
            }
        }
        if (!class_exists('Aysa_Wallet_Manager')) {
            return;
        }

        $wallet = Aysa_Wallet_Manager::get_instance();
        $meta_payload = is_array($meta) ? $meta : array();
        $meta_payload['server_side'] = true;
        $meta_payload['user_id'] = get_current_user_id();
        $event = array(
            'endpoint' => (string) $endpoint,
            'cost_cents' => $cost_cents,
            'meta' => $meta_payload,
            'ts' => time(),
        );

        // Keep UX non-blocking: usage is always queued remotely; local debit is best-effort.
        $wallet->enqueue_usage_event($project_id, (string) $endpoint, $cost_cents, $meta_payload, intval($event['ts']));
        $wallet->apply_credit_delta(
            $project_id,
            -abs($cost_cents),
            'usage',
            'wp_plugin_server',
            (string) $reference,
            null,
            $meta_payload
        );

        if (method_exists($wallet, 'push_usage_events_now')) {
            $wallet->push_usage_events_now($project_id, array($event));
        } elseif (method_exists($wallet, 'flush_usage_queue')) {
            $wallet->flush_usage_queue($project_id);
        }
    }

    private function aysa_cost_usd_to_milli_cents($costUsd) {
        if (!is_numeric($costUsd)) {
            return 0;
        }
        $val = (float) $costUsd;
        if ($val <= 0) {
            return 0;
        }
        return max(0, intval(round($val * 100000)));
    }

    private function aysa_wallet_track_action_usage($project_id, $action_key, $reference = '', $meta = array(), $fallback_cost_cents = 0, $endpoint = '') {
        $project_id = intval($project_id);
        if ($project_id <= 0 || trim((string) $action_key) === '') {
            return;
        }

        $action_key = sanitize_key((string) $action_key);
        $billing_owner = $this->aysa_wallet_action_billing_owner($action_key);
        if ($billing_owner === 'display_only') {
            $this->aysa_wallet_log_skipped_action_usage($project_id, $action_key, $reference, $meta, 'display_only');
            return;
        }
        if ($billing_owner === 'agent8_api_owned' && $this->aysa_wallet_usage_context_is_agent8($meta, $endpoint)) {
            $this->aysa_wallet_log_skipped_action_usage($project_id, $action_key, $reference, $meta, 'agent8_api_owned');
            return;
        }

        if (!class_exists('Aysa_Wallet_Manager')) {
            $wallet_file = AYSA_PLUGIN_DIR . 'includes/class-aysa-wallet-manager.php';
            if (file_exists($wallet_file)) {
                require_once $wallet_file;
            }
        }
        if (!class_exists('Aysa_Wallet_Manager')) {
            return;
        }

        $wallet = Aysa_Wallet_Manager::get_instance();
        if (!method_exists($wallet, 'track_action_usage')) {
            // Fallback for older wallet manager versions.
            $this->aysa_wallet_track_server_usage($project_id, $endpoint, intval($fallback_cost_cents), $reference, $meta);
            return;
        }

        $wallet->track_action_usage(
            $project_id,
            $action_key,
            (string) $reference,
            is_array($meta) ? $meta : array(),
            intval($fallback_cost_cents),
            (string) $endpoint
        );
    }

    private function aysa_wallet_action_billing_owner($action_key) {
        $action_key = sanitize_key((string) $action_key);

        $agent8_api_owned = array(
            'aysa_adverlink_approve_publisher_article_draft' => true,
            'aysa_adverlink_backlink_audit' => true,
            'aysa_adverlink_create_publisher_article_draft' => true,
            'aysa_adverlink_exchange_activate_link' => true,
            'aysa_adverlink_exchange_change_state' => true,
            'aysa_adverlink_exchange_create_request' => true,
            'aysa_adverlink_exchange_refresh_verification' => true,
            'aysa_adverlink_exchange_respond_request' => true,
            'aysa_adverlink_exchange_submit_publication' => true,
            'aysa_adverlink_save_article_draft' => true,
            'aysa_adverlink_update_publisher_article_draft' => true,
            'aysa_link_exchange_create_request' => true,
            'aysa_link_exchange_match_sites' => true,
            'aysa_monitoring_rank_refresh_start_background' => true,
        );
        if (isset($agent8_api_owned[$action_key])) {
            return 'agent8_api_owned';
        }

        $display_only = array(
            'aysa_get_link_orders' => true,
            'aysa_get_order_details' => true,
            'aysa_get_wordtolink_api_url' => true,
            'aysa_keyword_discovery_get_associations' => true,
            'aysa_monitoring_keywords_overview' => true,
            'aysa_onpage_get_associations' => true,
        );
        if (isset($display_only[$action_key])) {
            return 'display_only';
        }

        $needs_decision = array(
            'aysa_get_redirect_suggestions' => true,
            'aysa_missing_page_generate_meta' => true,
            'aysa_missing_pages_refactor_titles' => true,
            'aysa_technical_pagespeed_summary' => true,
        );
        if (isset($needs_decision[$action_key])) {
            return 'needs_decision';
        }

        return 'plugin_wallet';
    }

    private function aysa_wallet_usage_context_is_agent8($meta, $endpoint = '') {
        $endpoint = strtolower((string) $endpoint);
        if ($endpoint !== '' && (strpos($endpoint, 'agent8') !== false || strpos($endpoint, 'agent8v3') !== false)) {
            return true;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed callback inventory: this read-only/core lifecycle request is authenticated by WordPress or an enclosing guard and performs no unapproved write.
        $request_action = isset($_REQUEST['action']) ? sanitize_key((string) wp_unslash($_REQUEST['action'])) : '';
        if ($request_action !== '' && strpos($request_action, 'aysa_agent8') === 0) {
            return true;
        }

        if (!is_array($meta)) {
            return false;
        }
        foreach (array('source', 'module', 'origin', 'caller', 'trigger') as $key) {
            $value = strtolower(trim((string) ($meta[$key] ?? '')));
            if ($value !== '' && (strpos($value, 'agent8') !== false || strpos($value, 'agent8v3') !== false || strpos($value, 'v3') === 0)) {
                return true;
            }
        }

        return false;
    }

    private function aysa_wallet_log_skipped_action_usage($project_id, $action_key, $reference, $meta, $reason) {
        $payload = array(
            'event' => 'plugin_wallet_skipped_' . sanitize_key((string) $reason),
            'project_id' => intval($project_id),
            'action_key' => sanitize_key((string) $action_key),
            'reference' => sanitize_text_field((string) $reference),
            'source' => is_array($meta) ? sanitize_text_field((string) ($meta['source'] ?? '')) : '',
        );
        do_action('aysa_wallet_action_usage_skipped', $payload);
        if (defined('WP_DEBUG') && WP_DEBUG) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Release-reviewed diagnostic only; the message contains no credentials or request payloads and runs only in opt-in debugging or the administrative uninstall lifecycle.
            error_log('[AYSA wallet] skipped local usage: ' . wp_json_encode($payload));
        }
    }

    private function aysa_technical_site_audit_billed_cents_from_pages($pages_crawled) {
        $pages_crawled = max(0, intval($pages_crawled));
        return 500;
    }

    private function aysa_wallet_usage_reference_exists($project_id, $reference) {
        global $wpdb;

        $project_id = intval($project_id);
        $reference = trim((string) $reference);
        if ($project_id <= 0 || $reference === '') {
            return false;
        }

        $table = esc_sql($wpdb->prefix . 'aysa_wallet_ledger');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $existing_id = $wpdb->get_var(
            $wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT id
" .
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
                "                 FROM {$table}
                 WHERE project_id = %d
                   AND entry_type = %s
                   AND reference = %s
                 ORDER BY id DESC
                 LIMIT 1",
                $project_id,
                'usage',
                $reference
            )
        );

        return intval($existing_id) > 0;
    }

    private function aysa_technical_site_audit_bill_if_needed($project_id, array $state, array $audit_payload = array()) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return $state;
        }

        $billing_strategy = sanitize_key((string) ($state['billing_strategy'] ?? ''));
        if ($billing_strategy !== 'site_audit_fixed_500_v1') {
            return $state;
        }

        $job_id = sanitize_text_field((string) ($state['job_id'] ?? ''));
        if ($job_id === '') {
            return $state;
        }

        $billed_job_id = sanitize_text_field((string) ($state['billing_job_id'] ?? ''));
        if ($billed_job_id === $job_id && intval($state['billing_cents'] ?? 0) > 0) {
            return $state;
        }

        $snapshot = !empty($state['audit_snapshot']) && is_array($state['audit_snapshot']) ? $state['audit_snapshot'] : array();
        $pages_crawled = max(
            intval($audit_payload['pages_crawled'] ?? 0),
            intval($snapshot['pages_crawled'] ?? 0)
        );
        $status = sanitize_key((string) ($state['status'] ?? ''));
        if ($pages_crawled <= 0 && !in_array($status, array('completed', 'failed'), true)) {
            return $state;
        }

        $billed_cents = $this->aysa_technical_site_audit_billed_cents_from_pages($pages_crawled);
        $billing_reference = 'technical_site_audit_start:' . $job_id;
        if ($this->aysa_wallet_usage_reference_exists($project_id, $billing_reference)) {
            $state['billing_job_id'] = $job_id;
            $state['billing_cents'] = $billed_cents;
            $state['billing_pages_crawled'] = $pages_crawled;
            $state['billing_billed_at'] = current_time('mysql');
            $state['billing_status'] = 'billed';
            return $state;
        }

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_technical_site_audit_start',
            $billing_reference,
            array(
                'module' => 'technical_optimization',
                'target_url' => (string) ($state['target_url'] ?? ''),
                'job_id' => $job_id,
                'provider' => (string) ($state['provider'] ?? 'screamingfrog'),
                'pages_crawled' => $pages_crawled,
                'billing_rule' => 'fixed_500_site_audit_only',
                'override_cost_cents' => $billed_cents,
            ),
            $billed_cents,
            '/v1/technical/screamingfrog/start'
        );

        $state['billing_job_id'] = $job_id;
        $state['billing_cents'] = $billed_cents;
        $state['billing_pages_crawled'] = $pages_crawled;
        $state['billing_billed_at'] = current_time('mysql');
        $state['billing_status'] = 'billed';

        return $state;
    }

    private function aysa_wallet_log_endpoint_usage($project_id, $endpoint, $usage = array(), $action_key = '', $reference = '', $meta = array()) {
        $project_id = intval($project_id);
        $endpoint = (string) $endpoint;
        if ($project_id <= 0 || $endpoint === '' || !is_array($usage) || empty($usage)) {
            return;
        }

        if (!class_exists('Aysa_Wallet_Manager')) {
            $wallet_file = AYSA_PLUGIN_DIR . 'includes/class-aysa-wallet-manager.php';
            if (file_exists($wallet_file)) {
                require_once $wallet_file;
            }
        }
        if (!class_exists('Aysa_Wallet_Manager')) {
            return;
        }

        $wallet = Aysa_Wallet_Manager::get_instance();
        if (!method_exists($wallet, 'log_endpoint_usage')) {
            return;
        }

        $billed_cost_cents = 0;
        if (method_exists($wallet, 'get_action_cost_cents')) {
            $billed_cost_cents = intval($wallet->get_action_cost_cents($action_key, 0));
        }

        $wallet->log_endpoint_usage(
            $project_id,
            $endpoint,
            $usage,
            sanitize_key((string) $action_key),
            (string) $reference,
            $billed_cost_cents,
            is_array($meta) ? $meta : array()
        );
    }

    private function aysa_wallet_bill_provider_usage($project_id, $endpoint, $usage = array(), $reference = '', $meta = array(), $pricing_multiplier = 5) {
        $project_id = intval($project_id);
        if ($project_id <= 0 || !is_array($usage) || empty($usage)) {
            return 0;
        }

        $provider_milli = 0;
        if (isset($usage['provider_cost_milli_cents'])) {
            $provider_milli = max(0, intval($usage['provider_cost_milli_cents']));
        } elseif (isset($usage['provider_cost_usd'])) {
            $provider_milli = $this->aysa_cost_usd_to_milli_cents($usage['provider_cost_usd']);
        } elseif (isset($usage['cost_usd'])) {
            $provider_milli = $this->aysa_cost_usd_to_milli_cents($usage['cost_usd']);
        }
        if ($provider_milli <= 0) {
            return 0;
        }

        $multiplier = max(1, intval($pricing_multiplier));
        $billed_cents = intval(ceil(($provider_milli * $multiplier) / 1000));
        if ($billed_cents <= 0) {
            $billed_cents = 1;
        }

        $meta_payload = is_array($meta) ? $meta : array();
        $meta_payload['provider_cost_milli_cents'] = $provider_milli;
        $meta_payload['pricing_multiplier'] = $multiplier;
        $meta_payload['provider'] = (string) ($usage['provider'] ?? 'openai');
        $meta_payload['model'] = (string) ($usage['model'] ?? '');
        $meta_payload['input_tokens'] = intval($usage['input_tokens'] ?? ($usage['prompt_tokens'] ?? 0));
        $meta_payload['output_tokens'] = intval($usage['output_tokens'] ?? ($usage['completion_tokens'] ?? 0));
        $meta_payload['total_tokens'] = intval($usage['total_tokens'] ?? 0);

        $this->aysa_wallet_track_server_usage(
            $project_id,
            (string) $endpoint,
            $billed_cents,
            (string) $reference,
            $meta_payload
        );

        return $billed_cents;
    }

    private function aysa_usage_action_key_from_path($path) {
        $path = rtrim((string) $path, '/');
        $map = array(
            '/v1/content-strategy/generate' => 'aysa_content_strategy_generate',
            '/v1/content/strategy/generate' => 'aysa_content_strategy_generate',
            '/v1/content/missing-page/meta' => 'aysa_missing_page_generate_meta',
            '/v1/content/seo-suggestion' => 'aysa_onpage_generate_suggestion',
            '/v1/setup/general/autofill' => 'aysa_setup_general_autofill',
            '/v1/conversation/setup/message' => 'aysa_setup_conversation_message',
            '/v1/conversation/adjustments/message' => 'aysa_adjustments_conversation_message',
            '/v1/conversation/monitoring/message' => 'aysa_monitoring_conversation_message',
            '/v1/technical/screamingfrog/start' => 'aysa_technical_site_audit_start',
            '/v1/technical/screamingfrog/status' => 'aysa_technical_site_audit_status',
            '/v1/technical/screamingfrog/results' => 'aysa_technical_site_audit_results',
            '/v1/technical/pagespeed/summary' => 'aysa_technical_pagespeed_summary',
            '/v1/keywords/intent/bulk' => 'aysa_keyword_discovery_intent_batch',
            '/v1/keywords/suggestions' => 'aysa_keyword_discovery_keyword_suggestions_refresh',
            '/v1/keywords/overview' => 'aysa_monitoring_keywords_overview',
            '/v1/keywords/gap' => 'aysa_competitor_gap_keywords_gap',
            '/v1/competitors/identify/from-seeds' => 'aysa_competitor_identification_seed_competitors',
            '/adverlink/v1/public/portals' => 'aysa_adverlink_list_portals',
            '/adverlink/v1/public/orders/link-issues' => 'aysa_adverlink_get_link_issues',
            '/adverlink/v1/public/orders/create' => 'aysa_adverlink_create_order',
            '/adverlink/v1/public/orders/track' => 'aysa_adverlink_track_order',
            '/adverlink/v1/public/orders/renew' => 'aysa_adverlink_subscription_renew',
            '/adverlink/v1/exchange/portals' => 'aysa_adverlink_exchange_list_portals',
            '/adverlink/v1/exchange/requests/create' => 'aysa_adverlink_exchange_create_request',
            '/adverlink/v1/exchange/requests/list' => 'aysa_adverlink_exchange_list_requests',
            '/adverlink/v1/exchange/requests/get' => 'aysa_adverlink_exchange_get_request',
            '/adverlink/v1/exchange/requests/respond' => 'aysa_adverlink_exchange_respond_request',
            '/adverlink/v1/exchange/requests/simulate-accept' => 'aysa_adverlink_exchange_simulate_accept',
            '/adverlink/v1/exchange/requests/activate' => 'aysa_adverlink_exchange_activate_link',
            '/adverlink/v1/exchange/requests/publication' => 'aysa_adverlink_exchange_submit_publication',
            '/adverlink/v1/exchange/requests/verify' => 'aysa_adverlink_exchange_refresh_verification',
            '/adverlink/v1/exchange/requests/state' => 'aysa_adverlink_exchange_change_state',
        );
        if (isset($map[$path])) {
            return $map[$path];
        }
        if (strpos($path, '/blog-content/') === 0) {
            return 'aysa_content_generate_article';
        }
        return '';
    }

    public function handle_technical_site_audit_start() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $target_url = isset($_POST['target_url']) ? esc_url_raw((string) wp_unslash($_POST['target_url'])) : '';
        if ($target_url === '') {
            $target_url = $this->aysa_technical_resolve_target_url($project_id);
        }

        $job = $this->aysa_project_jobs_enqueue(
            $project_id,
            'technical',
            'site_audit',
            array(
                'project_id' => $project_id,
                'url' => $target_url,
                'job_id' => 'wp_' . $project_id . '_' . gmdate('Ymd_His'),
                'mode' => 'baseline_full',
            ),
            array(
                'dedupe_key' => md5(wp_json_encode(array($project_id, $target_url, 'baseline_full'))),
                'input_summary' => array(
                    'target_url' => $target_url,
                ),
            )
        );
        if (is_wp_error($job)) {
            wp_send_json_error($job->get_error_message());
            return;
        }

        $state = get_option($this->aysa_technical_site_audit_state_key($project_id), array());
        if (!is_array($state)) {
            $state = array();
        }
        $state = array_merge($state, array(
            'project_id' => $project_id,
            'job_id' => sanitize_text_field((string) ($job['job_id'] ?? '')),
            'status' => sanitize_key((string) ($job['status'] ?? 'queued')),
            'target_url' => esc_url_raw($target_url),
            'updated_at' => current_time('mysql'),
            'provider' => 'aysa_site_audit',
            'source' => 'legacy_plugin',
            'orchestration' => 'project_jobs',
        ));
        update_option($this->aysa_technical_site_audit_state_key($project_id), $state, false);

        wp_send_json_success(array(
            'state' => $state,
            'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
            'project_job_status' => array(
                'run_id' => (string) ($job['job_id'] ?? ''),
                'status' => (string) ($job['status'] ?? 'queued'),
                'worker_ref' => (string) ($job['worker_ref'] ?? ''),
                'worker_status' => (string) ($job['worker_status'] ?? ''),
                'error' => (string) ($job['error_summary'] ?? ''),
            ),
            'project_job_rack' => $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state($project_id)),
        ));
    }

    public function handle_technical_site_audit_status() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        $state_key = $this->aysa_technical_site_audit_state_key($project_id);
        $state = get_option($state_key, array());
        if (!is_array($state)) {
            $state = array();
        }
        $local_snapshot = $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id);
        $has_local_snapshot = $this->aysa_technical_site_audit_snapshot_has_data($local_snapshot);
        // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $force_refresh = !empty($_POST['refresh']) && (string) wp_unslash($_POST['refresh']) !== '0';
        $cached_audit = (!empty($state['audit_snapshot']) && is_array($state['audit_snapshot'])) ? $state['audit_snapshot'] : array();
        $cached_files = (!empty($state['result_files']) && is_array($state['result_files'])) ? $state['result_files'] : array();

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $job_id = isset($_POST['job_id']) ? sanitize_text_field((string) wp_unslash($_POST['job_id'])) : '';
        if ($job_id === '' && !empty($state['job_id'])) {
            $job_id = sanitize_text_field((string) $state['job_id']);
        }
        if (!$force_refresh) {
            $cached_state = array_merge(array(
                'project_id' => $project_id,
                'job_id' => $job_id,
                'status' => $job_id === '' ? 'idle' : (string) ($state['status'] ?? 'unknown'),
            ), $state);
            wp_send_json_success(array(
                'state' => $cached_state,
                'cached' => true,
                'snapshot' => $local_snapshot,
                'audit' => $cached_audit,
                'files' => $cached_files,
            ));
            return;
        }

        if (($state['provider'] ?? '') === 'local_wordpress') {
            $cached_state = array_merge(array(
                'project_id' => $project_id,
                'job_id' => $job_id,
                'status' => (string) ($state['status'] ?? 'completed'),
            ), $state);
            wp_send_json_success(array(
                'state' => $cached_state,
                'cached' => true,
                'snapshot' => $local_snapshot,
                'audit' => $cached_audit,
                'files' => $cached_files,
            ));
            return;
        }

        if ($job_id === '') {
            wp_send_json_success(array(
                'state' => array(
                    'project_id' => $project_id,
                    'job_id' => '',
                    'status' => $has_local_snapshot ? (string) ($state['status'] ?? 'completed') : 'idle',
                ),
                'cached' => true,
                'snapshot' => $local_snapshot,
                'audit' => $cached_audit,
                'files' => $cached_files,
            ));
            return;
        }

        $result = $this->aysa_broker_request('POST', '/v1/technical/screamingfrog/status', array('job_id' => $job_id), 45);
        if (is_wp_error($result)) {
            $fallback_state = array_merge(array(
                'project_id' => $project_id,
                'job_id' => $job_id,
                'status' => (string) ($state['status'] ?? 'unknown'),
            ), $state);
            wp_send_json_success(array(
                'state' => $fallback_state,
                'cached' => true,
                'stale' => true,
                'warning' => $this->aysa_humanize_broker_error($result, 'Broker unavailable') . ' — showing cached local snapshot.',
                'snapshot' => $local_snapshot,
                'audit' => $cached_audit,
                'files' => $cached_files,
            ));
            return;
        }

        $status = is_array($result) ? (string) ($result['status'] ?? 'unknown') : 'unknown';
        $state = array_merge($state, array(
            'project_id' => $project_id,
            'job_id' => $job_id,
            'status' => $status,
            'updated_at' => current_time('mysql'),
            'job_dir' => is_array($result) ? (string) ($result['job_dir'] ?? '') : '',
            'mode' => is_array($result) ? (string) ($result['mode'] ?? ($state['mode'] ?? 'baseline_full')) : (string) ($state['mode'] ?? 'baseline_full'),
            'config_path' => is_array($result) ? (string) ($result['config_path'] ?? ($state['config_path'] ?? '')) : (string) ($state['config_path'] ?? ''),
            'export_tabs' => is_array($result) ? (string) ($result['export_tabs'] ?? ($state['export_tabs'] ?? '')) : (string) ($state['export_tabs'] ?? ''),
            'bulk_exports' => is_array($result) ? (string) ($result['bulk_exports'] ?? ($state['bulk_exports'] ?? '')) : (string) ($state['bulk_exports'] ?? ''),
            'reports' => is_array($result) ? (string) ($result['reports'] ?? ($state['reports'] ?? '')) : (string) ($state['reports'] ?? ''),
            'artifacts' => is_array($result) && isset($result['artifacts']) && is_array($result['artifacts']) ? $result['artifacts'] : array(),
        ));
        $state = $this->aysa_technical_site_audit_merge_local_state($project_id, $state);
        update_option($state_key, $state, false);
        if (in_array((string) ($state['status'] ?? ''), array('completed', 'failed'), true)) {
            $this->aysa_project_jobs_finalize(
                $project_id,
                $this->aysa_project_jobs_make_worker_ref('technical_site_audit', 'site_audit', (string) ($state['job_id'] ?? '')),
                (string) ($state['status'] ?? 'failed'),
                isset($state['audit_snapshot']) && is_array($state['audit_snapshot']) ? $state['audit_snapshot'] : $state,
                (string) ($state['last_error'] ?? '')
            );
        }

        wp_send_json_success(array(
            'state' => $state,
            'broker' => $result,
            'cached' => false,
            'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
            'audit' => $cached_audit,
            'files' => $cached_files,
            'project_job_rack' => $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state($project_id)),
        ));
    }

    public function handle_technical_site_audit_results() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        $state_key = $this->aysa_technical_site_audit_state_key($project_id);
        $state = get_option($state_key, array());
        if (!is_array($state)) {
            $state = array();
        }
        $local_snapshot = $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id);
        $has_local_snapshot = $this->aysa_technical_site_audit_snapshot_has_data($local_snapshot);
        // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $force_refresh = !empty($_POST['refresh']) && (string) wp_unslash($_POST['refresh']) !== '0';
        $cached_audit = (!empty($state['audit_snapshot']) && is_array($state['audit_snapshot'])) ? $state['audit_snapshot'] : array();
        $cached_files = (!empty($state['result_files']) && is_array($state['result_files'])) ? $state['result_files'] : array();

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $job_id = isset($_POST['job_id']) ? sanitize_text_field((string) wp_unslash($_POST['job_id'])) : '';
        if ($job_id === '' && !empty($state['job_id'])) {
            $job_id = sanitize_text_field((string) $state['job_id']);
        }
        if (!$force_refresh && (!empty($cached_audit) || !empty($cached_files))) {
            $cached_state = array_merge(array(
                'project_id' => $project_id,
                'job_id' => $job_id,
                'status' => (string) ($state['status'] ?? ($job_id === '' ? 'idle' : 'unknown')),
            ), $state);
            $cached_state = $this->aysa_technical_site_audit_bill_if_needed($project_id, $cached_state, $cached_audit);
            if ($cached_state !== $state) {
                update_option($state_key, $cached_state, false);
            }
            wp_send_json_success(array(
                'state' => $cached_state,
                'cached' => true,
                'snapshot' => $local_snapshot,
                'audit' => $cached_audit,
                'files' => $cached_files,
            ));
            return;
        }

        if (($state['provider'] ?? '') === 'local_wordpress') {
            $cached_state = array_merge(array(
                'project_id' => $project_id,
                'job_id' => $job_id,
                'status' => (string) ($state['status'] ?? 'completed'),
            ), $state);
            wp_send_json_success(array(
                'state' => $cached_state,
                'cached' => true,
                'snapshot' => $local_snapshot,
                'audit' => $cached_audit,
                'files' => $cached_files,
            ));
            return;
        }

        if ($job_id === '') {
            if ($has_local_snapshot) {
                wp_send_json_success(array(
                    'state' => array_merge(array(
                        'project_id' => $project_id,
                        'job_id' => '',
                        'status' => (string) ($state['status'] ?? 'completed'),
                    ), $state),
                    'cached' => true,
                    'snapshot' => $local_snapshot,
                    'audit' => $cached_audit,
                    'files' => $cached_files,
                ));
                return;
            }
            wp_send_json_error('Missing job id');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $preview_rows = isset($_POST['preview_rows']) ? intval(wp_unslash($_POST['preview_rows'])) : 20;
        if ($preview_rows < 1) {
            $preview_rows = 20;
        }
        if ($preview_rows > 100) {
            $preview_rows = 100;
        }

        $result = $this->aysa_broker_request('POST', '/v1/technical/screamingfrog/results', array(
            'job_id' => $job_id,
            'preview_rows' => $preview_rows,
        ), 60);
        if (is_wp_error($result)) {
            if (!empty($cached_audit) || !empty($cached_files)) {
                $fallback_state = array_merge(array(
                    'project_id' => $project_id,
                    'job_id' => $job_id,
                    'status' => (string) ($state['status'] ?? 'unknown'),
                ), $state);
                wp_send_json_success(array(
                    'state' => $fallback_state,
                    'cached' => true,
                    'stale' => true,
                    'warning' => $this->aysa_humanize_broker_error($result, 'Broker unavailable') . ' — showing cached local snapshot.',
                    'snapshot' => $local_snapshot,
                    'audit' => $cached_audit,
                    'files' => $cached_files,
                ));
                return;
            }
            wp_send_json_error($this->aysa_humanize_broker_error($result, 'Could not load site audit results.'));
            return;
        }

        $files = is_array($result) && isset($result['files']) && is_array($result['files']) ? $result['files'] : array();
        $audit_payload = is_array($result) && isset($result['audit']) && is_array($result['audit']) ? $result['audit'] : array();
        $state = array_merge($state, array(
            'project_id' => $project_id,
            'job_id' => $job_id,
            'status' => is_array($result) ? (string) ($result['status'] ?? 'unknown') : 'unknown',
            'updated_at' => current_time('mysql'),
            'mode' => is_array($result) ? (string) ($result['mode'] ?? ($state['mode'] ?? 'baseline_full')) : (string) ($state['mode'] ?? 'baseline_full'),
            'config_path' => is_array($result) ? (string) ($result['config_path'] ?? ($state['config_path'] ?? '')) : (string) ($state['config_path'] ?? ''),
            'export_tabs' => is_array($result) ? (string) ($result['export_tabs'] ?? ($state['export_tabs'] ?? '')) : (string) ($state['export_tabs'] ?? ''),
            'bulk_exports' => is_array($result) ? (string) ($result['bulk_exports'] ?? ($state['bulk_exports'] ?? '')) : (string) ($state['bulk_exports'] ?? ''),
            'reports' => is_array($result) ? (string) ($result['reports'] ?? ($state['reports'] ?? '')) : (string) ($state['reports'] ?? ''),
            'result_count' => count($files),
            'latest_output_dir' => is_array($result) ? (string) ($result['latest_output_dir'] ?? '') : '',
            'audit_snapshot' => $audit_payload,
            'result_files' => $files,
        ));
        $state = $this->aysa_technical_site_audit_merge_local_state($project_id, $state);
        $state = $this->aysa_technical_site_audit_bill_if_needed($project_id, $state, $audit_payload);
        $version_id = $this->aysa_sf_site_audit_persist_version($project_id, $state);
        if ($version_id > 0) {
            $latest_version = $this->aysa_sf_site_audit_get_latest_version_row($project_id);
            if (is_array($latest_version) && !empty($latest_version)) {
                $state['version_id'] = intval($latest_version['id'] ?? 0);
                $state['version_number'] = intval($latest_version['version_number'] ?? 0);
            }
        }
        update_option($state_key, $state, false);

        wp_send_json_success(array(
            'state' => $state,
            'snapshot' => $this->aysa_adjustments_get_technical_site_audit_snapshot($project_id),
            'broker' => $result,
            'cached' => false,
            'audit' => $audit_payload,
            'files' => $files,
        ));
    }

    public function handle_agentmarkup_check() {
        check_ajax_referer('aysa_nonce', 'nonce');
        if (!$this->aysa_current_user_can_workspace()) {
            wp_send_json_error(array('message' => 'Access denied.'), 403);
            return;
        }

        $project_id = $this->aysa_resolve_project_id_with_local_fallback(0);
        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'The verified AYSA project is not available for this site.'), 422);
            return;
        }

        $site_url = esc_url_raw(home_url('/'));
        $site_host = $this->aysa_agent8v3_site_host();
        if ($site_url === '' || $site_host === '') {
            wp_send_json_error(array('message' => 'The verified site URL is unavailable.'), 422);
            return;
        }

        $result = $this->aysa_broker_request('POST', '/v1/technical/agentmarkup/check', array(
            'project_id' => $project_id,
            'site_host' => $site_host,
            'url' => $site_url,
            'authorized' => true,
        ), 60);
        if (is_wp_error($result)) {
            $message = $this->aysa_humanize_broker_error($result, 'The AgentMarkup check could not be completed. Please retry.');
            wp_send_json_error(array('message' => $message), 502);
            return;
        }

        wp_send_json_success($result);
    }

    public function handle_technical_pagespeed_summary() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $refresh = !empty($_POST['refresh']) && (string) wp_unslash($_POST['refresh']) !== '0';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $target_url = isset($_POST['target_url']) ? esc_url_raw((string) wp_unslash($_POST['target_url'])) : '';
        if ($target_url === '') {
            $target_url = $this->aysa_technical_resolve_target_url();
        }

        $state_key = $this->aysa_technical_pagespeed_state_key($project_id);
        $cached = get_option($state_key, array());
        if (!is_array($cached)) {
            $cached = array();
        }

        $max_age_seconds = 6 * HOUR_IN_SECONDS;
        if (!$refresh && !empty($cached['updated_at_unix']) && (time() - intval($cached['updated_at_unix']) < $max_age_seconds) && !empty($cached['data']) && is_array($cached['data'])) {
            wp_send_json_success(array(
                'cached' => true,
                'data' => $cached['data'],
                'updated_at' => $cached['updated_at'],
            ));
            return;
        }

        $payload = array(
            'project_id' => $project_id,
            'url' => $target_url,
            'force_refresh' => $refresh,
        );
        $result = $this->aysa_broker_request('POST', '/v1/technical/pagespeed/summary', $payload, 90);
        if (is_wp_error($result)) {
            $broker_error = $result;
            $fallback = $this->aysa_pagespeed_summary_fallback($target_url);
            if (is_wp_error($fallback)) {
                $legacy = $this->aysa_pagespeed_summary_from_legacy_endpoints($project_id, $target_url, $refresh);
                if (!is_wp_error($legacy)) {
                    $legacy['warning'] = $broker_error->get_error_message() . ' | ' . $fallback->get_error_message();
                    $result = $legacy;
                } else {
                $result = array(
                    'ok' => true,
                    'requested_at' => gmdate('c'),
                    'url' => $target_url,
                    'overview' => array(
                        'pageSpeedDesktop' => 0,
                        'pageSpeedMobile' => 0,
                        'mobileFriendly' => 0,
                        'indexability' => 90,
                        'requestedAt' => gmdate('c'),
                        'overallScore' => 0,
                    ),
                    'desktop' => array(
                        'strategy' => 'desktop',
                        'requestedAt' => gmdate('c'),
                        'metrics' => array(),
                        'opportunities' => array(),
                    ),
                    'mobile' => array(
                        'strategy' => 'mobile',
                        'requestedAt' => gmdate('c'),
                        'metrics' => array(),
                        'opportunities' => array(),
                    ),
                    'conclusion' => array(
                        'overall_score' => 0,
                        'status' => 'needs_improvement',
                        'summary' => 'Google Page Speed data is temporarily unavailable. Retry after connectivity/API access is restored.',
                        'priority_actions' => array(),
                    ),
                    'warning' => $broker_error->get_error_message() . ' | ' . $fallback->get_error_message() . ' | ' . $legacy->get_error_message(),
                );
                }
            } else {
                $result = $fallback;
            }
        }

        // If the fresh result is degraded (0 scores due to quota/outage), prefer last valid cached dataset.
        if ($this->aysa_pagespeed_is_degraded_result($result) && !empty($cached['data']) && is_array($cached['data']) && !$this->aysa_pagespeed_is_degraded_result($cached['data'])) {
            $cached_data = $cached['data'];
            $warning = isset($result['warning']) ? (string) $result['warning'] : 'Live fetch failed; using last valid cached data.';
            $cached_data['warning'] = $warning;
            wp_send_json_success(array(
                'cached' => true,
                'stale' => true,
                'data' => $cached_data,
                'updated_at' => isset($cached['updated_at']) ? $cached['updated_at'] : '',
            ));
            return;
        }

        $state = array(
            'project_id' => $project_id,
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'data' => $result,
        );
        update_option($state_key, $state, false);
        // Bill only for explicit manual refresh actions, not passive tab/view loads.
        if ($refresh) {
            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_technical_pagespeed_summary',
                'technical_pagespeed_summary_manual_refresh',
                array(
                    'module' => 'technical_optimization',
                    'source' => 'live',
                    'target_url' => $target_url,
                    'refresh' => 1,
                ),
                48,
                '/v1/technical/pagespeed/summary'
            );
        }

        wp_send_json_success(array(
            'cached' => false,
            'data' => $result,
            'updated_at' => $state['updated_at'],
        ));
    }

    public function handle_export_pagespeed_pdf() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error(array('message' => 'Invalid nonce'));
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error(array('message' => 'Access denied'));
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Invalid project id'));
            return;
        }

        $snapshot = $this->aysa_adjustments_get_technical_pagespeed_snapshot($project_id);
        if (empty($snapshot['has_data'])) {
            $snapshot = $this->aysa_adjustments_run_technical_pagespeed_refresh($project_id, false);
        }
        if (!is_array($snapshot) || empty($snapshot['has_data'])) {
            wp_send_json_error(array('message' => 'PageSpeed data is not available yet. Refresh the analysis first.'));
            return;
        }

        $state = get_option($this->aysa_technical_pagespeed_state_key($project_id), array());
        if (!is_array($state)) {
            $state = array();
        }
        $state_data = isset($state['data']) && is_array($state['data']) ? $state['data'] : array();
        $raw_payload = isset($state_data['raw']) && is_array($state_data['raw']) ? $state_data['raw'] : array();
        if ($raw_payload === array() && (
            isset($state_data['overview'])
            || isset($state_data['desktop'])
            || isset($state_data['mobile'])
            || isset($state_data['conclusion'])
        )) {
            $raw_payload = $state_data;
        }

        $site_url = isset($snapshot['url']) && is_string($snapshot['url']) && trim($snapshot['url']) !== ''
            ? trim((string) $snapshot['url'])
            : $this->aysa_technical_resolve_target_url();
        $site_host = wp_parse_url($site_url, PHP_URL_HOST);
        if (!is_string($site_host) || trim($site_host) === '') {
            $site_host = wp_parse_url(home_url('/'), PHP_URL_HOST);
        }

        $lang_raw = (string) get_option('aysa_language', get_locale());
        $language_code = strtolower(substr($lang_raw, 0, 2));
        if (!in_array($language_code, array('ro', 'en'), true)) {
            $language_code = 'en';
        }

        $broker_payload = array(
            'project_id' => $project_id,
            'site_url' => $site_url,
            'site_host' => is_string($site_host) ? trim($site_host) : '',
            'brand_name' => sanitize_text_field((string) get_bloginfo('name')),
            'language' => $language_code,
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'force_regenerate' => !empty($_POST['force_regenerate']),
            'snapshot' => $snapshot,
            'raw_payload' => $raw_payload,
        );

        $job = $this->aysa_project_jobs_enqueue(
            $project_id,
            'technical',
            'pagespeed_report',
            $broker_payload,
            array(
                // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
                'dedupe_key' => md5(wp_json_encode(array($project_id, $site_url, !empty($_POST['force_regenerate'])))),
                'input_summary' => array(
                    'site_url' => $site_url,
                    'site_host' => is_string($site_host) ? trim($site_host) : '',
                ),
            )
        );

        wp_send_json_success(array(
            'job_id' => (string) ($job['job_id'] ?? ''),
            'status' => (string) ($job['status'] ?? 'queued'),
            'progress_label' => sanitize_text_field((string) (($job['result_summary']['progress_label'] ?? 'Queued'))),
            'worker_ref' => (string) ($job['worker_ref'] ?? ''),
            'worker_status' => (string) ($job['worker_status'] ?? ''),
            'project_job_rack' => $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state($project_id)),
        ));
    }

    public function handle_export_pagespeed_pdf_status() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error(array('message' => 'Invalid nonce'));
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error(array('message' => 'Access denied'));
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = $this->aysa_technical_schema_resolve_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Invalid project id'));
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $job_id = isset($_POST['job_id']) ? sanitize_text_field((string) wp_unslash($_POST['job_id'])) : '';
        if ($job_id === '') {
            wp_send_json_error(array('message' => 'Missing job id'));
            return;
        }

        $result = $this->aysa_broker_request('POST', '/v1/technical/pagespeed/report/status', array(
            'job_id' => $job_id,
            'project_id' => $project_id,
        ), 30);
        if (is_wp_error($result)) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Release-reviewed diagnostic only; the message contains no credentials or request payloads and runs only in opt-in debugging or the administrative uninstall lifecycle.
            error_log('[aysa_pagespeed_pdf] status_wp_error ' . wp_json_encode(array(
                'job_id' => $job_id,
                'message' => $result->get_error_message(),
            )));
            wp_send_json_error(array('message' => $result->get_error_message()));
            return;
        }
        if (!is_array($result) || empty($result['ok'])) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Release-reviewed diagnostic only; the message contains no credentials or request payloads and runs only in opt-in debugging or the administrative uninstall lifecycle.
            error_log('[aysa_pagespeed_pdf] status_broker_error ' . wp_json_encode(array(
                'job_id' => $job_id,
                'result' => $result,
            )));
            wp_send_json_error(array(
                'message' => isset($result['error']) ? (string) $result['error'] : 'PageSpeed report status failed.',
                'result' => $result,
            ));
            return;
        }
        if (intval($result['project_id'] ?? $project_id) !== $project_id) {
            wp_send_json_error(array('message' => 'PageSpeed report does not belong to this project.'));
            return;
        }

        $normalized_status = sanitize_key((string) ($result['status'] ?? 'queued'));
        if (in_array($normalized_status, array('completed', 'failed'), true)) {
            $this->aysa_project_jobs_finalize(
                $project_id,
                $this->aysa_project_jobs_make_worker_ref('pagespeed_report', 'pagespeed_report', sanitize_text_field((string) ($result['job_id'] ?? $job_id))),
                $normalized_status,
                isset($result['summary']) && is_array($result['summary']) ? $result['summary'] : $result,
                sanitize_text_field((string) ($result['error'] ?? ''))
            );
        }

        wp_send_json_success(array(
            'job_id' => sanitize_text_field((string) ($result['job_id'] ?? $job_id)),
            'status' => sanitize_text_field((string) ($result['status'] ?? 'queued')),
            'progress_label' => sanitize_text_field((string) ($result['progress_label'] ?? '')),
            'download_url' => esc_url_raw((string) ($result['download_url'] ?? '')),
            'file_name' => sanitize_text_field((string) ($result['file_name'] ?? 'aysa-pagespeed-report.pdf')),
            'summary' => isset($result['summary']) && is_array($result['summary']) ? $result['summary'] : array(),
            'updated_at' => sanitize_text_field((string) ($result['updated_at'] ?? '')),
            'error' => sanitize_text_field((string) ($result['error'] ?? '')),
            'project_job_rack' => $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state($project_id)),
        ));
    }

    public function handle_pagespeed_report_email() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error(array('message' => 'Invalid nonce'));
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error(array('message' => 'Access denied'));
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $recipient = isset($_POST['recipient_email']) ? sanitize_email((string) wp_unslash($_POST['recipient_email'])) : '';
        if ($project_id <= 0) {
            wp_send_json_error(array('message' => 'Invalid project id'));
            return;
        }
        if ($recipient === '' || !is_email($recipient)) {
            wp_send_json_error(array('message' => 'Valid recipient email required.'));
            return;
        }

        $snapshot = $this->aysa_adjustments_get_technical_pagespeed_snapshot($project_id);
        if (empty($snapshot['has_data'])) {
            wp_send_json_error(array('message' => 'PageSpeed snapshot missing. Refresh PageSpeed first.'));
            return;
        }

        $site_url = isset($snapshot['url']) && is_string($snapshot['url']) && trim($snapshot['url']) !== ''
            ? trim((string) $snapshot['url'])
            : $this->aysa_technical_resolve_target_url();
        $site_host = wp_parse_url($site_url, PHP_URL_HOST);
        if (!is_string($site_host) || trim($site_host) === '') {
            $site_host = wp_parse_url(home_url('/'), PHP_URL_HOST);
        }

        $lang_raw = (string) get_option('aysa_language', get_locale());
        $language_code = strtolower(substr($lang_raw, 0, 2));
        if (!in_array($language_code, array('ro', 'en'), true)) {
            $language_code = 'en';
        }

        $state = get_option($this->aysa_technical_pagespeed_state_key($project_id), array());
        if (!is_array($state)) {
            $state = array();
        }
        $state_data = isset($state['data']) && is_array($state['data']) ? $state['data'] : array();
        $raw_payload = isset($state_data['raw']) && is_array($state_data['raw']) ? $state_data['raw'] : array();
        if ($raw_payload === array() && (
            isset($state_data['overview'])
            || isset($state_data['desktop'])
            || isset($state_data['mobile'])
            || isset($state_data['conclusion'])
        )) {
            $raw_payload = $state_data;
        }

        $report = $this->aysa_broker_request('POST', '/v1/technical/pagespeed/report', array(
            'project_id' => $project_id,
            'site_url' => $site_url,
            'site_host' => is_string($site_host) ? trim($site_host) : '',
            'brand_name' => sanitize_text_field((string) get_bloginfo('name')),
            'language' => $language_code,
            'snapshot' => $snapshot,
            'raw_payload' => $raw_payload,
        ), 60);
        if (is_wp_error($report) || !is_array($report) || empty($report['ok'])) {
            wp_send_json_error(array(
                'message' => is_wp_error($report) ? $report->get_error_message() : 'Could not generate the PageSpeed report for email.',
            ));
            return;
        }

        $download_url = esc_url_raw((string) ($report['download_url'] ?? ''));
        $subject = 'AYSA PageSpeed AI Report';
        $body_lines = array(
            'The latest PageSpeed AI report is ready.',
            '',
            'Website: ' . $site_url,
            'Overall score: ' . intval($report['summary']['overall_score'] ?? 0),
            'Desktop score: ' . intval($report['summary']['desktop_score'] ?? 0),
            'Mobile score: ' . intval($report['summary']['mobile_score'] ?? 0),
            'Verdict: ' . sanitize_text_field((string) ($report['summary']['verdict'] ?? '')),
            '',
            'Download report: ' . $download_url,
            'Generated at: ' . sanitize_text_field((string) ($report['created_at'] ?? current_time('mysql'))),
        );

        $mail_result = $this->aysa_agent8_send_communication_email(
            $recipient,
            $subject,
            implode("\n", $body_lines),
            array(
                'module' => 'technical_pagespeed',
                'trigger' => 'pagespeed_report_ready',
                'project_id' => $project_id,
                'action_ref' => 'pagespeed_report_ajax:' . $project_id,
                'deep_link' => $download_url,
                'reply_approval_supported' => false,
            )
        );
        if (empty($mail_result['ok'])) {
            wp_send_json_error(array('message' => 'Email send failed.'));
            return;
        }

        $state_key = $this->aysa_technical_pagespeed_state_key($project_id);
        $state = get_option($state_key, array());
        if (!is_array($state)) {
            $state = array();
        }
        $state['last_report_email'] = array(
            'recipient_email' => $recipient,
            'download_url' => $download_url,
            'file_name' => sanitize_text_field((string) ($report['file_name'] ?? 'aysa-pagespeed-report.pdf')),
            'sent_at' => current_time('mysql'),
        );
        update_option($state_key, $state, false);

        wp_send_json_success(array(
            'recipient_email' => $recipient,
            'download_url' => $download_url,
            'file_name' => sanitize_text_field((string) ($report['file_name'] ?? 'aysa-pagespeed-report.pdf')),
            'sent_at' => sanitize_text_field((string) ($state['last_report_email']['sent_at'] ?? current_time('mysql'))),
        ));
    }

    public function handle_technical_sitemap_robots_audit() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $refresh = !empty($_POST['refresh']) && (string) wp_unslash($_POST['refresh']) !== '0';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $target_url = isset($_POST['target_url']) ? esc_url_raw((string) wp_unslash($_POST['target_url'])) : '';
        if ($target_url === '') {
            $target_url = $this->aysa_technical_resolve_target_url();
        }

        $state_key = $this->aysa_technical_sitemap_robots_state_key($project_id);
        $cached = get_option($state_key, array());
        if (!is_array($cached)) {
            $cached = array();
        }

        $max_age_seconds = 6 * HOUR_IN_SECONDS;
        if (
            !$refresh &&
            !empty($cached['updated_at_unix']) &&
            (time() - intval($cached['updated_at_unix']) < $max_age_seconds) &&
            !empty($cached['data']) &&
            is_array($cached['data'])
        ) {
            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_technical_sitemap_robots_audit',
                'technical_sitemap_robots_audit',
                array(
                    'module' => 'technical_optimization',
                    'source' => 'cache',
                    'target_url' => $target_url,
                ),
                24,
                '/v1/technical/sitemap-robots/audit'
            );
            wp_send_json_success(array(
                'cached' => true,
                'data' => $cached['data'],
                'updated_at' => isset($cached['updated_at']) ? $cached['updated_at'] : '',
            ));
            return;
        }

        $audit = $this->aysa_technical_build_sitemap_robots_audit($target_url);
        $state = array(
            'project_id' => $project_id,
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'data' => $audit,
        );
        update_option($state_key, $state, false);
        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_technical_sitemap_robots_audit',
            'technical_sitemap_robots_audit',
            array(
                'module' => 'technical_optimization',
                'source' => 'live',
                'target_url' => $target_url,
            ),
            24,
            '/v1/technical/sitemap-robots/audit'
        );

        wp_send_json_success(array(
            'cached' => false,
            'data' => $audit,
            'updated_at' => $state['updated_at'],
        ));
    }

    public function handle_technical_schema_snapshot() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        $snapshot = $this->aysa_schema_inventory_get_ui_snapshot($project_id);
        $refresh_job = $this->aysa_technical_schema_get_current_refresh_job($project_id);
        if ($refresh_job && in_array((string) ($refresh_job['status'] ?? ''), array('queued', 'running'), true)) {
            $snapshot['status'] = 'reconciling';
            $snapshot['job'] = $this->aysa_technical_schema_get_job_response($refresh_job);
        }
        $snapshot['resolved_project_id'] = $project_id;
        $snapshot['snapshot_updated_at'] = current_time('mysql');

        wp_send_json_success(array(
            'snapshot' => $snapshot,
        ));
    }

    private function aysa_technical_schema_start_queued_job($payload) {
        $project_id = intval($payload['project_id'] ?? 0);
        $mode = sanitize_key((string) ($payload['mode'] ?? 'validate'));
        $scope = sanitize_key((string) ($payload['scope'] ?? 'selection'));
        $items = isset($payload['items']) && is_array($payload['items']) ? $payload['items'] : array();
        if ($project_id <= 0 || empty($items)) {
            return new WP_Error('invalid_technical_schema_request', 'No valid schema rows were selected.');
        }

        $job = $this->aysa_technical_schema_create_job($project_id, $mode, $scope, $items, 'worker');
        $this->aysa_technical_schema_schedule_job((string) $job['job_id'], 1);

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_technical_schema_job_start',
            'technical_schema_job_start',
            array(
                'module' => 'technical_schema',
                'mode' => $mode,
                'scope' => $scope,
                'selected_count' => count($items),
                'job_id' => (string) ($job['job_id'] ?? ''),
            ),
            8,
            '/v1/technical/schema/job/start'
        );

        return array(
            'status' => sanitize_key((string) ($job['status'] ?? 'queued')),
            'worker_ref' => $this->aysa_project_jobs_make_worker_ref('technical_schema', 'schema_job', (string) ($job['job_id'] ?? '')),
            'worker_status' => sanitize_key((string) ($job['status'] ?? 'queued')),
            'result_summary' => array('selected_count' => count($items)),
            'started_at' => time(),
        );
    }

    public function handle_technical_schema_refresh() {
        $this->handle_technical_schema_refresh_start();
    }

    public function handle_technical_schema_rewrite_start() {
        $_POST['mode'] = 'rewrite_recommended';
        $_POST['scope'] = 'selection';
        $this->handle_technical_schema_job_start();
    }

    public function handle_technical_schema_rewrite_status() {
        $this->handle_technical_schema_job_status();
    }

    public function handle_technical_sitemap_generate() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $target_url = isset($_POST['target_url']) ? esc_url_raw((string) wp_unslash($_POST['target_url'])) : '';
        if ($target_url === '') {
            $target_url = $this->aysa_technical_resolve_target_url();
        }

        $generation = $this->aysa_technical_generate_own_sitemap($target_url);
        $audit = $this->aysa_technical_build_sitemap_robots_audit($target_url);
        $state = array(
            'project_id' => $project_id,
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'data' => $audit,
        );
        update_option($this->aysa_technical_sitemap_robots_state_key($project_id), $state, false);
        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_technical_sitemap_generate',
            'technical_sitemap_generate',
            array(
                'module' => 'technical_optimization',
                'target_url' => $target_url,
                'mode' => sanitize_text_field((string) ($generation['mode'] ?? '')),
                'entries_count' => intval($generation['entries_count'] ?? 0),
            ),
            18,
            '/v1/technical/sitemap/generate'
        );

        wp_send_json_success(array(
            'generated' => true,
            'generation' => $generation,
            'data' => $audit,
            'updated_at' => $state['updated_at'],
        ));
    }

    public function handle_technical_robots_save() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $target_url = isset($_POST['target_url']) ? esc_url_raw((string) wp_unslash($_POST['target_url'])) : '';
        if ($target_url === '') {
            $target_url = $this->aysa_technical_resolve_target_url();
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow. Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $robots_content = isset($_POST['robots_content']) ? (string) wp_unslash($_POST['robots_content']) : '';
        $saved = $this->aysa_technical_save_robots_content($robots_content);
        if (is_wp_error($saved)) {
            wp_send_json_error($saved->get_error_message());
            return;
        }

        $audit = $this->aysa_technical_build_sitemap_robots_audit($target_url);
        $state = array(
            'project_id' => $project_id,
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'data' => $audit,
        );
        update_option($this->aysa_technical_sitemap_robots_state_key($project_id), $state, false);
        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_technical_robots_save',
            'technical_robots_save',
            array(
                'module' => 'technical_optimization',
                'target_url' => $target_url,
            ),
            12,
            '/v1/technical/robots/save'
        );

        wp_send_json_success(array(
            'saved' => true,
            'data' => $audit,
            'updated_at' => $state['updated_at'],
        ));
    }

    public function handle_technical_robots_restore_default() {
        if (!$this->aysa_verify_technical_audit_nonce()) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $project_id = isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0;
        $project_id = $this->aysa_technical_schema_resolve_project_id($project_id);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $target_url = isset($_POST['target_url']) ? esc_url_raw((string) wp_unslash($_POST['target_url'])) : '';
        if ($target_url === '') {
            $target_url = $this->aysa_technical_resolve_target_url();
        }

        $sitemaps = $this->aysa_technical_fetch_sitemap_candidates($target_url);
        $primary_sitemap = '';
        foreach ($sitemaps as $row) {
            if (!empty($row['reachable']) && !empty($row['url'])) {
                $primary_sitemap = (string) $row['url'];
                break;
            }
        }

        $content = $this->aysa_technical_robots_default_content($target_url, $primary_sitemap);
        $saved = $this->aysa_technical_save_robots_content($content);
        if (is_wp_error($saved)) {
            wp_send_json_error($saved->get_error_message());
            return;
        }

        $audit = $this->aysa_technical_build_sitemap_robots_audit($target_url);
        $state = array(
            'project_id' => $project_id,
            'updated_at' => current_time('mysql'),
            'updated_at_unix' => time(),
            'data' => $audit,
        );
        update_option($this->aysa_technical_sitemap_robots_state_key($project_id), $state, false);
        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_technical_robots_restore_default',
            'technical_robots_restore_default',
            array(
                'module' => 'technical_optimization',
                'target_url' => $target_url,
            ),
            12,
            '/v1/technical/robots/restore-default'
        );

        wp_send_json_success(array(
            'restored' => true,
            'data' => $audit,
            'updated_at' => $state['updated_at'],
        ));
    }

    private function aysa_pagespeed_is_degraded_result($result) {
        if (!is_array($result)) {
            return true;
        }
        $overview = isset($result['overview']) && is_array($result['overview']) ? $result['overview'] : array();
        $desktop = intval($overview['pageSpeedDesktop'] ?? 0);
        $mobile = intval($overview['pageSpeedMobile'] ?? 0);
        $overall = intval($overview['overallScore'] ?? 0);
        $has_metrics = !empty($result['desktop']['metrics']) || !empty($result['mobile']['metrics']);
        if ($has_metrics) {
            return false;
        }
        return ($desktop <= 0 && $mobile <= 0 && $overall <= 0);
    }

    private function aysa_pagespeed_summary_fallback($target_url) {
        $desktop_raw = $this->aysa_pagespeed_fetch_strategy($target_url, 'desktop');
        if (is_wp_error($desktop_raw)) {
            return $desktop_raw;
        }
        $mobile_raw = $this->aysa_pagespeed_fetch_strategy($target_url, 'mobile');
        if (is_wp_error($mobile_raw)) {
            return $mobile_raw;
        }

        $desktop = $this->aysa_pagespeed_normalize_strategy($desktop_raw, 'desktop');
        $mobile = $this->aysa_pagespeed_normalize_strategy($mobile_raw, 'mobile');
        $desktop_score = intval(round(floatval($desktop['metrics']['performance']['score_percent'] ?? 0)));
        $mobile_score = intval(round(floatval($mobile['metrics']['performance']['score_percent'] ?? 0)));
        $overall = intval(round(($desktop_score + $mobile_score) / 2));

        $priority = array();
        $source_pool = array_merge(
            is_array($desktop['opportunities']) ? $desktop['opportunities'] : array(),
            is_array($mobile['opportunities']) ? $mobile['opportunities'] : array()
        );
        usort($source_pool, static function($a, $b) {
            return intval($b['impact'] ?? 0) <=> intval($a['impact'] ?? 0);
        });
        foreach ($source_pool as $row) {
            if (!is_array($row)) {
                continue;
            }
            $id = sanitize_key((string) ($row['id'] ?? ''));
            if ($id === '' || isset($priority[$id])) {
                continue;
            }
            $priority[$id] = array(
                'id' => $id,
                'title' => (string) ($row['title'] ?? $id),
                'displayValue' => (string) ($row['displayValue'] ?? ''),
                'impact' => intval($row['impact'] ?? 0),
            );
            if (count($priority) >= 5) {
                break;
            }
        }

        $summary = 'Good baseline with optimization room.';
        $status = 'good';
        if ($overall >= 90) {
            $summary = 'Excellent speed profile on desktop and mobile.';
            $status = 'excellent';
        } elseif ($overall < 70) {
            $summary = 'Performance needs urgent improvements, especially on mobile.';
            $status = $overall < 50 ? 'critical' : 'needs_improvement';
        }

        return array(
            'ok' => true,
            'requested_at' => gmdate('c'),
            'url' => $target_url,
            'overview' => array(
                'pageSpeedDesktop' => $desktop_score,
                'pageSpeedMobile' => $mobile_score,
                'mobileFriendly' => $mobile_score,
                'indexability' => 90,
                'requestedAt' => gmdate('c'),
                'overallScore' => $overall,
            ),
            'desktop' => $desktop,
            'mobile' => $mobile,
            'conclusion' => array(
                'overall_score' => $overall,
                'status' => $status,
                'summary' => $summary,
                'priority_actions' => array_values($priority),
            ),
        );
    }

    private function aysa_pagespeed_fetch_strategy($target_url, $strategy = 'desktop') {
        $query = array(
            'url' => (string) $target_url,
            'strategy' => (string) $strategy,
            'category' => 'PERFORMANCE',
        );
        $api_key = $this->aysa_get_pagespeed_api_key();
        if ($api_key !== '') {
            $query['key'] = $api_key;
        }
        $endpoint = 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed?' . http_build_query($query);
        $resp = wp_remote_get($endpoint, array(
            'timeout' => 45,
            'headers' => array('Accept' => 'application/json'),
        ));
        if (is_wp_error($resp)) {
            return $resp;
        }
        $code = intval(wp_remote_retrieve_response_code($resp));
        $body = (string) wp_remote_retrieve_body($resp);
        $decoded = json_decode($body, true);
        if (!is_array($decoded)) {
            return new WP_Error('pagespeed_invalid_json', 'Invalid JSON from Google PageSpeed API.');
        }
        if ($code < 200 || $code >= 300 || isset($decoded['error'])) {
            $msg = isset($decoded['error']['message']) ? (string) $decoded['error']['message'] : 'Google PageSpeed API error.';
            return new WP_Error('pagespeed_http_error', $msg);
        }
        return $decoded;
    }

    private function aysa_pagespeed_normalize_strategy($raw, $strategy = 'desktop') {
        if (!is_array($raw)) {
            return array(
                'strategy' => (string) $strategy,
                'requestedAt' => gmdate('c'),
                'metrics' => array(),
                'opportunities' => array(),
            );
        }
        $lhr = (isset($raw['lighthouseResult']) && is_array($raw['lighthouseResult'])) ? $raw['lighthouseResult'] : array();
        $categories = (isset($lhr['categories']) && is_array($lhr['categories'])) ? $lhr['categories'] : array();
        $audits = (isset($lhr['audits']) && is_array($lhr['audits'])) ? $lhr['audits'] : array();
        $perf_score = floatval($categories['performance']['score'] ?? 0) * 100;
        $metrics = array(
            'performance' => array(
                'score' => round(floatval($categories['performance']['score'] ?? 0), 4),
                'score_percent' => round($perf_score, 0),
            ),
            'firstContentfulPaint' => isset($audits['first-contentful-paint']['numericValue']) ? floatval($audits['first-contentful-paint']['numericValue']) : null,
            'largestContentfulPaint' => isset($audits['largest-contentful-paint']['numericValue']) ? floatval($audits['largest-contentful-paint']['numericValue']) : null,
            'totalBlockingTime' => isset($audits['total-blocking-time']['numericValue']) ? floatval($audits['total-blocking-time']['numericValue']) : null,
            'cumulativeLayoutShift' => isset($audits['cumulative-layout-shift']['numericValue']) ? floatval($audits['cumulative-layout-shift']['numericValue']) : null,
            'speedIndex' => isset($audits['speed-index']['numericValue']) ? floatval($audits['speed-index']['numericValue']) : null,
            'interactive' => isset($audits['interactive']['numericValue']) ? floatval($audits['interactive']['numericValue']) : null,
            'totalByteWeight' => isset($audits['total-byte-weight']['numericValue']) ? floatval($audits['total-byte-weight']['numericValue']) : null,
        );

        $opportunities = array();
        foreach ($audits as $id => $audit) {
            if (!is_array($audit)) {
                continue;
            }
            $savings_ms = isset($audit['details']['overallSavingsMs']) ? floatval($audit['details']['overallSavingsMs']) : 0;
            $savings_bytes = isset($audit['details']['overallSavingsBytes']) ? floatval($audit['details']['overallSavingsBytes']) : 0;
            $score = isset($audit['score']) ? floatval($audit['score']) : 1.0;
            if ($savings_ms <= 0 && $savings_bytes <= 0 && $score >= 0.9) {
                continue;
            }
            $impact = intval(round($savings_ms + ($savings_bytes / 1024)));
            $opportunities[] = array(
                'id' => (string) $id,
                'title' => (string) ($audit['title'] ?? $id),
                'description' => (string) ($audit['description'] ?? ''),
                'displayValue' => (string) ($audit['displayValue'] ?? 'Needs improvement'),
                'savings_ms' => $savings_ms,
                'savings_bytes' => $savings_bytes,
                'score' => $score,
                'impact' => $impact,
            );
        }
        usort($opportunities, static function($a, $b) {
            return intval($b['impact'] ?? 0) <=> intval($a['impact'] ?? 0);
        });
        $opportunities = array_slice($opportunities, 0, 12);

        return array(
            'strategy' => (string) $strategy,
            'requestedAt' => gmdate('c'),
            'metrics' => $metrics,
            'opportunities' => $opportunities,
        );
    }

    private function aysa_get_pagespeed_api_key() {
        if (defined('AYSA_PAGESPEED_API_KEY')) {
            $val = trim((string) AYSA_PAGESPEED_API_KEY);
            if ($val !== '') {
                return $val;
            }
        }
        $opt = get_option('aysa_pagespeed_api_key', '');
        if (is_string($opt)) {
            $opt = trim($opt);
            if ($opt !== '') {
                return $opt;
            }
        }

        // Local dev convenience: reuse broker config key when available.
        $candidate_paths = array(
            trailingslashit(ABSPATH) . '../../../api_projects/api.aysa.ai/.secure/config.php',
            trailingslashit(ABSPATH) . '../../api_projects/api.aysa.ai/.secure/config.php',
        );
        foreach ($candidate_paths as $path) {
            $real = realpath($path);
            if (!$real || !is_readable($real)) {
                continue;
            }
            $cfg = @include $real;
            if (!is_array($cfg)) {
                continue;
            }
            $key = isset($cfg['google']['pagespeed_api_key']) ? trim((string) $cfg['google']['pagespeed_api_key']) : '';
            if ($key !== '') {
                return $key;
            }
        }
        return '';
    }

    private function aysa_pagespeed_summary_from_legacy_endpoints($project_id, $target_url, $refresh = false) {
        if (!class_exists('Aysa_API_Config')) {
            return new WP_Error('pagespeed_legacy_missing_config', 'Aysa_API_Config is not available.');
        }

        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return new WP_Error('pagespeed_legacy_missing_project', 'Missing project id for legacy PageSpeed endpoint.');
        }

        if ($refresh) {
            $this->aysa_pagespeed_legacy_post($project_id, $target_url, 'desktop');
            $this->aysa_pagespeed_legacy_post($project_id, $target_url, 'mobile');
            $this->aysa_pagespeed_legacy_post_overview($project_id, $target_url);
        }

        $overview_url = Aysa_API_Config::get_endpoint_url_with_params('pagespeed_overview_get', array('projectId' => $project_id));
        $metrics_url = Aysa_API_Config::get_endpoint_url_with_params('pagespeed_metrics_get', array('projectId' => $project_id));
        if (!$overview_url && !$metrics_url) {
            return new WP_Error('pagespeed_legacy_missing_endpoint', 'Legacy PageSpeed endpoints are not configured.');
        }

        $overview_rows = $overview_url ? $this->aysa_pagespeed_legacy_get_json($overview_url) : array();
        $metrics_rows = $metrics_url ? $this->aysa_pagespeed_legacy_get_json($metrics_url) : array();

        $overview_record = $this->aysa_pagespeed_pick_latest_overview($overview_rows);
        $desktop_record = $this->aysa_pagespeed_pick_latest_metrics_by_strategy($metrics_rows, 'desktop');
        $mobile_record = $this->aysa_pagespeed_pick_latest_metrics_by_strategy($metrics_rows, 'mobile');

        if (!$desktop_record && !$mobile_record && $overview_record && isset($overview_record['metrics']) && is_array($overview_record['metrics'])) {
            $desktop_record = $overview_record;
            $mobile_record = $overview_record;
        }
        if (!$desktop_record && !$mobile_record) {
            return new WP_Error('pagespeed_legacy_empty', 'No legacy PageSpeed records are available yet.');
        }
        if (!$desktop_record && $mobile_record) {
            $desktop_record = $mobile_record;
        }
        if (!$mobile_record && $desktop_record) {
            $mobile_record = $desktop_record;
        }

        $desktop = $this->aysa_pagespeed_normalize_legacy_metrics_record($desktop_record, 'desktop');
        $mobile = $this->aysa_pagespeed_normalize_legacy_metrics_record($mobile_record, 'mobile');

        $desktop_score = intval(round(floatval($desktop['metrics']['performance']['score_percent'] ?? 0)));
        $mobile_score = intval(round(floatval($mobile['metrics']['performance']['score_percent'] ?? 0)));

        $overview = is_array($overview_record) ? $overview_record : array();
        $overall = isset($overview['overallScore']) ? intval($overview['overallScore']) : intval(round(($desktop_score + $mobile_score) / 2));
        $mobile_friendly = isset($overview['mobileFriendly']) ? intval($overview['mobileFriendly']) : $mobile_score;
        $indexability = isset($overview['indexability']) ? intval($overview['indexability']) : 90;

        $priority = array();
        $pool = array_merge(
            is_array($desktop['opportunities']) ? $desktop['opportunities'] : array(),
            is_array($mobile['opportunities']) ? $mobile['opportunities'] : array()
        );
        usort($pool, static function($a, $b) {
            return intval($b['impact'] ?? 0) <=> intval($a['impact'] ?? 0);
        });
        foreach ($pool as $row) {
            if (!is_array($row)) {
                continue;
            }
            $id = sanitize_key((string) ($row['id'] ?? ''));
            if ($id === '' || isset($priority[$id])) {
                continue;
            }
            $priority[$id] = array(
                'id' => $id,
                'title' => (string) ($row['title'] ?? $id),
                'displayValue' => (string) ($row['displayValue'] ?? ''),
                'impact' => intval($row['impact'] ?? 0),
            );
            if (count($priority) >= 5) {
                break;
            }
        }

        $summary = 'Good baseline with optimization room.';
        $status = 'good';
        if ($overall >= 90) {
            $summary = 'Excellent speed profile on desktop and mobile.';
            $status = 'excellent';
        } elseif ($overall < 70) {
            $summary = 'Performance needs urgent improvements, especially on mobile.';
            $status = $overall < 50 ? 'critical' : 'needs_improvement';
        }

        return array(
            'ok' => true,
            'requested_at' => gmdate('c'),
            'url' => $target_url,
            'overview' => array(
                'pageSpeedDesktop' => $desktop_score,
                'pageSpeedMobile' => $mobile_score,
                'mobileFriendly' => $mobile_friendly,
                'indexability' => $indexability,
                'requestedAt' => (string) ($overview['requestedAt'] ?? gmdate('c')),
                'overallScore' => $overall,
            ),
            'desktop' => $desktop,
            'mobile' => $mobile,
            'conclusion' => array(
                'overall_score' => $overall,
                'status' => $status,
                'summary' => $summary,
                'priority_actions' => array_values($priority),
            ),
            'source' => 'legacy_pagespeed_api',
        );
    }

    private function aysa_pagespeed_legacy_get_json($url) {
        if (!is_string($url) || trim($url) === '') {
            return array();
        }
        $resp = wp_remote_get($url, array(
            'timeout' => 30,
            'headers' => array(
                'Accept' => 'application/json',
            ),
        ));
        if (is_wp_error($resp)) {
            return array();
        }
        $body = (string) wp_remote_retrieve_body($resp);
        $decoded = json_decode($body, true);
        if (is_array($decoded)) {
            return $decoded;
        }
        return array();
    }

    private function aysa_pagespeed_legacy_post($project_id, $target_url, $strategy = 'desktop') {
        $post_url = class_exists('Aysa_API_Config') ? Aysa_API_Config::get_endpoint_url('pagespeed_metrics') : false;
        if (!$post_url) {
            return;
        }
        wp_remote_post($post_url, array(
            'timeout' => 45,
            'headers' => array(
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ),
            'body' => wp_json_encode(array(
                'projectId' => intval($project_id),
                'url' => (string) $target_url,
                'strategy' => (string) $strategy,
            )),
        ));
    }

    private function aysa_pagespeed_legacy_post_overview($project_id, $target_url) {
        $post_url = class_exists('Aysa_API_Config') ? Aysa_API_Config::get_endpoint_url('pagespeed_overview') : false;
        if (!$post_url) {
            return;
        }
        wp_remote_post($post_url, array(
            'timeout' => 45,
            'headers' => array(
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ),
            'body' => wp_json_encode(array(
                'projectId' => intval($project_id),
                'url' => (string) $target_url,
            )),
        ));
    }

    private function aysa_pagespeed_pick_latest_overview($data) {
        $rows = $this->aysa_pagespeed_as_row_list($data);
        $best = null;
        $best_ts = 0;
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            if (!isset($row['pageSpeedDesktop']) && !isset($row['pageSpeedMobile']) && !isset($row['overallScore'])) {
                continue;
            }
            $ts = strtotime((string) ($row['requestedAt'] ?? ''));
            if ($ts === false) {
                $ts = 0;
            }
            if ($best === null || $ts >= $best_ts) {
                $best = $row;
                $best_ts = $ts;
            }
        }
        return is_array($best) ? $best : null;
    }

    private function aysa_pagespeed_pick_latest_metrics_by_strategy($data, $strategy = 'desktop') {
        $rows = $this->aysa_pagespeed_as_row_list($data);
        $best = null;
        $best_ts = 0;
        foreach ($rows as $row) {
            if (!is_array($row) || !isset($row['metrics']) || !is_array($row['metrics'])) {
                continue;
            }
            $row_strategy = strtolower((string) ($row['strategy'] ?? ''));
            if ($row_strategy !== '' && $row_strategy !== strtolower((string) $strategy)) {
                continue;
            }
            $ts = strtotime((string) ($row['requestedAt'] ?? ''));
            if ($ts === false) {
                $ts = 0;
            }
            if ($best === null || $ts >= $best_ts) {
                $best = $row;
                $best_ts = $ts;
            }
        }
        return is_array($best) ? $best : null;
    }

    private function aysa_pagespeed_as_row_list($data) {
        if (!is_array($data)) {
            return array();
        }
        if (isset($data[0]) && is_array($data[0])) {
            return $data;
        }
        return array($data);
    }

    private function aysa_pagespeed_normalize_legacy_metrics_record($record, $strategy = 'desktop') {
        if (!is_array($record)) {
            return array(
                'strategy' => (string) $strategy,
                'requestedAt' => gmdate('c'),
                'metrics' => array(
                    'performance' => array(
                        'score' => 0,
                        'score_percent' => 0,
                    ),
                ),
                'opportunities' => array(),
            );
        }
        $record['strategy'] = (string) ($record['strategy'] ?? $strategy);
        $record['requestedAt'] = (string) ($record['requestedAt'] ?? gmdate('c'));
        if (!isset($record['metrics']) || !is_array($record['metrics'])) {
            $record['metrics'] = array();
        }
        if (!isset($record['metrics']['performance']) || !is_array($record['metrics']['performance'])) {
            $record['metrics']['performance'] = array();
        }
        $score = $record['metrics']['performance']['score'] ?? null;
        $score_percent = $record['metrics']['performance']['score_percent'] ?? null;
        if ($score_percent === null && is_numeric($score)) {
            $score_val = floatval($score);
            $score_percent = $score_val <= 1 ? $score_val * 100 : $score_val;
        } elseif ($score_percent !== null) {
            $score_percent = floatval($score_percent);
        } else {
            $score_percent = 0;
        }
        if ($score === null) {
            $score = floatval($score_percent) / 100;
        }
        $record['metrics']['performance']['score'] = round(floatval($score), 4);
        $record['metrics']['performance']['score_percent'] = round(floatval($score_percent), 0);
        if (!isset($record['opportunities']) || !is_array($record['opportunities'])) {
            $record['opportunities'] = array();
        }
        return $record;
    }

    private function aysa_normalize_link_issue_row($row) {
        if (!is_array($row)) {
            return array();
        }

        $pick = function ($keys) use ($row) {
            return $this->aysa_find_first_string_by_keys_recursive($row, $keys);
        };

        $http_code = $pick(array('http_status_code', 'httpStatusCode', 'status_code'));
        $is_valid = $pick(array('is_valid', 'isValid'));
        $timeline_event = $pick(array('event', 'event_type', 'type'));
        $message = $pick(array(
            'issue_message',
            'message',
            'reason',
            'details',
            'error_message',
            'verification_message',
            'status_message'
        ));
        $issue_type = $pick(array('issue_type', 'type', 'category', 'reason_code', 'verification_status'));
        $status = $pick(array('status', 'issue_status', 'verification_status', 'state'));

        $detail_parts = array();
        if ($issue_type !== '') {
            $detail_parts[] = $issue_type;
        }
        if ($message !== '') {
            $detail_parts[] = $message;
        }
        if ($http_code !== '') {
            $detail_parts[] = 'HTTP ' . $http_code;
        }
        if ($is_valid !== '') {
            $detail_parts[] = 'Valid: ' . $is_valid;
        }
        if ($timeline_event !== '') {
            $detail_parts[] = 'Event: ' . $timeline_event;
        }
        $issue_summary = implode(' | ', array_unique(array_filter($detail_parts)));

        return array(
            'order_ref' => $pick(array('order_ref', 'orderRef', 'reference', 'ref')),
            'portal_domain' => $pick(array('portal_domain', 'portalDomain', 'portal', 'domain', 'publisher_domain')),
            'published_keyword' => $pick(array('published_keyword', 'keyword', 'anchor_text', 'anchorText')),
            'published_on_url' => $pick(array('published_on_url', 'source_url', 'sourceUrl', 'published_url', 'page_url', 'url')),
            'linked_to_url' => $pick(array('linked_to_url', 'target_url', 'targetUrl', 'destination_url', 'link_to_url')),
            'status' => $status,
            'issue_type' => $issue_type,
            'issue_message' => $message,
            'issue_summary' => $issue_summary,
            'http_status_code' => $http_code,
            'is_valid' => $is_valid,
            'detected_at' => $pick(array('detected_at', 'detectedAt', 'created_at', 'updated_at', 'checked_at', 'last_checked_at')),
            'raw' => $row,
        );
    }

    private function aysa_extract_link_issues_items($decoded) {
        if (!is_array($decoded)) {
            return array();
        }

        if (isset($decoded['data']) && is_array($decoded['data'])) {
            return $decoded['data'];
        }
        if (isset($decoded['items']) && is_array($decoded['items'])) {
            return $decoded['items'];
        }
        if (isset($decoded['issues']) && is_array($decoded['issues'])) {
            return $decoded['issues'];
        }
        if (isset($decoded['link_issues']) && is_array($decoded['link_issues'])) {
            return $decoded['link_issues'];
        }
        if (array_keys($decoded) === range(0, count($decoded) - 1)) {
            return $decoded;
        }

        return array();
    }

    private function aysa_fetch_adverlink_link_issues($project_id) {
        $creds = $this->aysa_get_adverlink_credentials();
        $api_key = $creds['api_key'];
        $advertiser_org_id = $creds['org_id'];

        if ($project_id <= 0) {
            return new WP_Error('missing_fields', 'Missing project_id.');
        }

        $credentials_check = $this->aysa_validate_adverlink_marketplace_credentials($creds);
        if (is_wp_error($credentials_check)) {
            return $credentials_check;
        }

        $decoded = $this->aysa_broker_request('POST', '/adverlink/v1/public/orders/link-issues', array(
            'project_id' => intval($project_id),
            'api_key' => $api_key,
            'advertiser_org_id' => $advertiser_org_id,
        ), 60);
        if (is_wp_error($decoded)) {
            return $decoded;
        }

        $items = $this->aysa_extract_link_issues_items($decoded);
        $normalized = array();
        foreach ($items as $row) {
            $normalized_row = $this->aysa_normalize_link_issue_row($row);
            if (!empty($normalized_row)) {
                $normalized[] = $normalized_row;
            }
        }

        return array(
            'items' => array_slice($normalized, 0, 500),
            'raw' => $decoded,
        );
    }

    private function aysa_save_adverlink_link_issues($project_id, $items, $source = 'manual') {
        if (!is_array($items)) {
            $items = array();
        }
        update_option($this->aysa_get_adverlink_link_issues_key($project_id), array_values($items), false);
        update_option($this->aysa_get_adverlink_link_issues_sync_key($project_id), array(
            'synced_at' => current_time('mysql'),
            'source' => sanitize_text_field((string) $source),
            'count' => count($items),
        ), false);
    }

    private function aysa_find_first_string_by_keys_recursive($data, $keys, $depth = 0) {
        if ($depth > 8 || !is_array($data)) {
            return '';
        }
        foreach ($keys as $k) {
            if (isset($data[$k]) && is_scalar($data[$k])) {
                $v = trim((string) $data[$k]);
                if ($v !== '') {
                    return $v;
                }
            }
        }
        foreach ($data as $value) {
            if (is_array($value)) {
                $found = $this->aysa_find_first_string_by_keys_recursive($value, $keys, $depth + 1);
                if ($found !== '') {
                    return $found;
                }
            }
        }
        return '';
    }

    private function aysa_find_first_array_by_key_recursive($data, $target_key, $depth = 0) {
        if ($depth > 8 || !is_array($data)) {
            return array();
        }
        if (isset($data[$target_key]) && is_array($data[$target_key])) {
            return $data[$target_key];
        }
        foreach ($data as $value) {
            if (is_array($value)) {
                $found = $this->aysa_find_first_array_by_key_recursive($value, $target_key, $depth + 1);
                if (!empty($found)) {
                    return $found;
                }
            }
        }
        return array();
    }

    private function aysa_extract_external_order_ref($payload) {
        $strict_ref = sanitize_text_field($this->aysa_find_first_string_by_keys_recursive($payload, array(
            'orderRef', 'order_ref', 'reference', 'ref'
        )));
        if ($strict_ref !== '') {
            return $strict_ref;
        }

        // Last-chance fallback: search for ADV-style refs in payload JSON
        if (is_array($payload)) {
            $json = wp_json_encode($payload);
            if (is_string($json) && preg_match('/\bADV\d+\b/i', $json, $m)) {
                return sanitize_text_field((string) $m[0]);
            }
        }

        return '';
    }

    private function aysa_extract_external_order_status($payload) {
        if (is_array($payload)) {
            if (isset($payload['status']) && is_scalar($payload['status'])) {
                $status = sanitize_text_field((string) $payload['status']);
                if ($status !== '') {
                    return $status;
                }
            }
            if (isset($payload['data']) && is_array($payload['data']) && isset($payload['data']['status'])) {
                $status = sanitize_text_field((string) $payload['data']['status']);
                if ($status !== '') {
                    return $status;
                }
            }
            if (isset($payload['order']) && is_array($payload['order']) && isset($payload['order']['status'])) {
                $status = sanitize_text_field((string) $payload['order']['status']);
                if ($status !== '') {
                    return $status;
                }
            }
        }

        return sanitize_text_field($this->aysa_find_first_string_by_keys_recursive($payload, array(
            'status', 'order_status'
        )));
    }

    private function aysa_extract_external_verification($payload) {
        $verification = $this->aysa_find_first_array_by_key_recursive($payload, 'verification');
        if (empty($verification)) {
            return array();
        }
        return array(
            'is_valid' => isset($verification['is_valid']) ? (bool) $verification['is_valid'] : null,
            'verified_at' => sanitize_text_field((string) ($verification['verified_at'] ?? '')),
            'http_status_code' => intval($verification['http_status_code'] ?? 0),
            'error_message' => sanitize_text_field((string) ($verification['error_message'] ?? ''))
        );
    }

    private function aysa_extract_external_subscription($payload) {
        $subscription = $this->aysa_find_first_array_by_key_recursive($payload, 'subscription');
        if (empty($subscription)) {
            return array();
        }

        $id = sanitize_text_field((string) ($subscription['id'] ?? ''));
        $status = sanitize_text_field((string) ($subscription['status'] ?? ''));
        $next_renewal_at = sanitize_text_field((string) ($subscription['next_renewal_at'] ?? ''));
        $grace_until = sanitize_text_field((string) ($subscription['grace_until'] ?? ''));
        $removed_at = sanitize_text_field((string) ($subscription['removed_at'] ?? ''));
        $cycle_days = intval($subscription['cycle_days'] ?? 0);

        if ($id === '' && $status === '' && $next_renewal_at === '') {
            return array();
        }

        return array(
            'id' => $id,
            'status' => $status,
            'cycle_days' => $cycle_days,
            'next_renewal_at' => $next_renewal_at,
            'grace_until' => $grace_until,
            'removed_at' => $removed_at,
        );
    }

    private function aysa_extract_external_order_cost_cents($payload) {
        if (!is_array($payload)) {
            return 0;
        }

        $contexts = array($payload);
        if (isset($payload['data']) && is_array($payload['data'])) {
            $contexts[] = $payload['data'];
        }
        if (isset($payload['order']) && is_array($payload['order'])) {
            $contexts[] = $payload['order'];
        }
        if (isset($payload['result']) && is_array($payload['result'])) {
            $contexts[] = $payload['result'];
        }
        $pricing = $this->aysa_find_first_array_by_key_recursive($payload, 'pricing');
        if (!empty($pricing)) {
            $contexts[] = $pricing;
        }

        $cents_keys = array(
            'total_cost_cents',
            'total_price_cents',
            'amount_cents',
            'price_cents',
            'cost_cents',
            'credits_cents',
            'credits'
        );
        foreach ($contexts as $ctx) {
            foreach ($cents_keys as $key) {
                if (isset($ctx[$key]) && is_scalar($ctx[$key])) {
                    $v = intval($ctx[$key]);
                    if ($v > 0) {
                        return $v;
                    }
                }
            }
        }

        $eur_keys = array(
            'sale_price_eur',
            'purchase_price_eur',
            'total_cost_eur',
            'total_price_eur',
            'amount_eur',
            'price_eur',
            'cost_eur',
            'total',
            'amount',
            'price',
            'cost'
        );
        foreach ($contexts as $ctx) {
            foreach ($eur_keys as $key) {
                if (!isset($ctx[$key]) || !is_scalar($ctx[$key])) {
                    continue;
                }
                $raw = trim((string) $ctx[$key]);
                if ($raw === '') {
                    continue;
                }
                if (preg_match('/([0-9]+(?:[\\.,][0-9]+)?)/', $raw, $m)) {
                    $num = str_replace(',', '.', (string) $m[1]);
                    $eur = floatval($num);
                    if ($eur > 0) {
                        return max(1, intval(round($eur * 100)));
                    }
                }
            }
        }

        return 0;
    }

    private function aysa_is_subscription_order($order) {
        if (!is_array($order)) {
            return false;
        }

        $billing_type = strtolower(trim((string) ($order['billing_type'] ?? '')));
        return $billing_type === 'monthly' || $billing_type === 'subscription';
    }

    private function aysa_is_subscription_renewal_blocked_status($status) {
        $s = strtoupper(trim((string) $status));
        if ($s === '') {
            return false;
        }

        return in_array($s, array(
            'CANCELED',
            'CANCELLED',
            'REJECTED',
            'FAILED',
            'ERROR',
            'AUTO_DISPUTED',
            'EXPIRED'
        ), true);
    }

    private function aysa_is_subscription_renewal_eligible_status($status) {
        $s = strtoupper(trim((string) $status));
        return in_array($s, array(
            'IN_PROGRESS',
            'COMPLETED'
        ), true);
    }

    private function aysa_subscription_next_renewal_timestamp($order) {
        if (!is_array($order) || !isset($order['subscription']) || !is_array($order['subscription'])) {
            return 0;
        }

        $value = trim((string) ($order['subscription']['next_renewal_at'] ?? ''));
        if ($value === '') {
            return 0;
        }

        $ts = strtotime($value);
        return $ts ? intval($ts) : 0;
    }

    private function aysa_save_adverlink_orders($project_id, $orders) {
        if (!is_array($orders)) {
            $orders = array();
        }
        update_option($this->aysa_get_adverlink_orders_key($project_id), array_values($orders), false);
    }

    private function aysa_is_terminal_order_status($status) {
        $s = strtoupper(trim((string) $status));
        if ($s === '') {
            return false;
        }
        return in_array($s, array(
            'COMPLETED',
            'CANCELED',
            'CANCELLED',
            'REJECTED',
            'FAILED',
            'ERROR',
            'AUTO_DISPUTED',
            'EXPIRED'
        ), true);
    }

    private function aysa_reconcile_reserved_credits($project_id, &$orders) {
        if (!is_array($orders)) {
            $orders = array();
        }

        $changed = false;
        $reserved_total = 0;
        foreach ($orders as &$order) {
            $status = (string) ($order['status'] ?? '');
            $estimated = max(0, intval($order['estimated_credits'] ?? 0));
            $released = !empty($order['credits_released']);

            if ($this->aysa_is_terminal_order_status($status)) {
                if (!$released) {
                    $order['credits_released'] = true;
                    $released = true;
                    $changed = true;
                }
            }

            if (!$released && $estimated > 0) {
                $reserved_total += $estimated;
            }
        }
        unset($order);

        $current_reserved = intval(get_option($this->aysa_get_reserved_credits_key($project_id), 0));
        if ($current_reserved !== $reserved_total) {
            update_option($this->aysa_get_reserved_credits_key($project_id), $reserved_total, false);
            $changed = true;
        }

        if ($changed) {
            $this->aysa_save_adverlink_orders($project_id, $orders);
        }

        return $reserved_total;
    }

    public function handle_adverlink_save_article_draft() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_project_id_with_local_fallback(intval(wp_unslash($_POST['project_id'] ?? 0)));
        $portal_id = sanitize_text_field((string) wp_unslash($_POST['portal_id'] ?? ''));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $draft_json = (string) wp_unslash($_POST['draft_json'] ?? '');
        if ($project_id <= 0 || $portal_id === '') {
            wp_send_json_error('Invalid project_id or portal_id');
            return;
        }

        $draft = array();
        if ($draft_json !== '') {
            $decoded = json_decode(wp_unslash($draft_json), true);
            if (is_array($decoded)) {
                $draft = $decoded;
            }
        }

        $allowed = array(
            'mode', 'target_url', 'keyword', 'title', 'lead', 'content_html',
            'remarks', 'rel_type', 'billing_type', 'photo_source',
            'linking_strategy', 'guarantee_36m',
            'li_keyword', 'li_target_search', 'li_target_url', 'li_target_title',
            'li_target_meta', 'li_rel_type', 'li_billing_type', 'li_remarks',
            'ads_title', 'ads_keyword', 'ads_target_search', 'ads_target_url',
            'ads_display_url', 'ads_target_article_url', 'ads_description',
            'ads_duration_months', 'ads_remarks'
        );
        $sanitized = array();
        foreach ($allowed as $k) {
            if (!array_key_exists($k, $draft)) {
                continue;
            }
            if ($k === 'target_url' || $k === 'li_target_url') {
                $sanitized[$k] = esc_url_raw((string) $draft[$k]);
            } elseif ($k === 'content_html') {
                $sanitized[$k] = (string) $draft[$k];
            } elseif ($k === 'guarantee_36m') {
                $sanitized[$k] = !empty($draft[$k]) ? 1 : 0;
            } elseif ($k === 'ads_duration_months') {
                $sanitized[$k] = max(1, min(12, intval($draft[$k])));
            } else {
                $sanitized[$k] = sanitize_textarea_field((string) $draft[$k]);
            }
        }
        $sanitized['updated_at'] = current_time('mysql');

        update_option($this->aysa_get_article_draft_key($project_id, $portal_id), $sanitized, false);
        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_adverlink_save_article_draft',
            'offpage_save_article_draft_' . md5($portal_id),
            array(
                'module' => 'offpage',
                'portal_id' => $portal_id,
            ),
            1,
            'wp_ajax/aysa_adverlink_save_article_draft'
        );
        wp_send_json_success(array('saved' => true, 'updated_at' => $sanitized['updated_at']));
    }

    public function handle_adverlink_get_article_draft() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_project_id_with_local_fallback(intval(wp_unslash($_POST['project_id'] ?? 0)));
        $portal_id = sanitize_text_field((string) wp_unslash($_POST['portal_id'] ?? ''));
        if ($project_id <= 0 || $portal_id === '') {
            wp_send_json_error('Invalid project_id or portal_id');
            return;
        }

        $draft = get_option($this->aysa_get_article_draft_key($project_id, $portal_id), array());
        if (!is_array($draft)) {
            $draft = array();
        }
        wp_send_json_success(array('draft' => $draft));
    }

    public function handle_adverlink_create_publisher_article_draft() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_project_id_with_local_fallback(intval(wp_unslash($_POST['project_id'] ?? 0)));
        $order_ref = sanitize_text_field((string) wp_unslash($_POST['order_ref'] ?? ''));
        if ($project_id <= 0 || $order_ref === '') {
            wp_send_json_error('Invalid project_id or order_ref');
            return;
        }

        $orders = $this->aysa_get_adverlink_orders($project_id);
        $matched_order = null;
        foreach ($orders as $order) {
            if (!is_array($order)) {
                continue;
            }
            $current_ref = sanitize_text_field((string) ($order['order_ref'] ?? ''));
            if ($current_ref === $order_ref) {
                $matched_order = $order;
                break;
            }
        }

        if (!is_array($matched_order)) {
            wp_send_json_error('Order not found.');
            return;
        }

        $result = $this->aysa_create_publisher_article_draft_for_order($project_id, $matched_order);
        if (is_wp_error($result)) {
            wp_send_json_error($result->get_error_message());
            return;
        }

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_adverlink_create_publisher_article_draft',
            'offpage_create_publisher_article_draft_' . md5($order_ref),
            array(
                'module' => 'offpage',
                'order_ref' => $order_ref,
            ),
            12,
            'wp_ajax/aysa_adverlink_create_publisher_article_draft'
        );

        wp_send_json_success($result);
    }

    public function handle_adverlink_list_publisher_article_drafts() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_project_id_with_local_fallback(intval(wp_unslash($_POST['project_id'] ?? 0)));
        $status = sanitize_key((string) wp_unslash($_POST['status'] ?? 'draft'));
        $limit = max(1, min(100, intval(wp_unslash($_POST['limit'] ?? 50))));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project_id');
            return;
        }

        $this->aysa_sync_publisher_article_drafts_from_orders($project_id);

        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/publisher-articles/list', array(
            'project_id' => $project_id,
            'status' => $status,
            'limit' => $limit,
        ), 60);

        if (is_wp_error($broker_result)) {
            wp_send_json_error($this->aysa_humanize_broker_error($broker_result, 'Could not load publisher article drafts.'));
            return;
        }

        $items = isset($broker_result['items']) && is_array($broker_result['items']) ? $broker_result['items'] : array();
        wp_send_json_success(array('items' => $items));
    }

    public function handle_adverlink_get_publisher_article_draft() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $draft_id = intval(wp_unslash($_POST['draft_id'] ?? 0));
        if ($draft_id <= 0) {
            wp_send_json_error('Invalid draft_id');
            return;
        }

        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/publisher-articles/get', array(
            'draft_id' => $draft_id,
        ), 60);

        if (is_wp_error($broker_result)) {
            wp_send_json_error($this->aysa_humanize_broker_error($broker_result, 'Could not load draft.'));
            return;
        }

        $draft = isset($broker_result['draft']) && is_array($broker_result['draft']) ? $broker_result['draft'] : array();
        wp_send_json_success(array('draft' => $draft));
    }

    public function handle_adverlink_update_publisher_article_draft() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $draft_id = intval(wp_unslash($_POST['draft_id'] ?? 0));
        if ($draft_id <= 0) {
            wp_send_json_error('Invalid draft_id');
            return;
        }

        $title = sanitize_text_field((string) wp_unslash($_POST['title'] ?? ''));
        $excerpt = sanitize_textarea_field((string) wp_unslash($_POST['excerpt'] ?? ''));
        $body_html = wp_kses_post((string) wp_unslash($_POST['body_html'] ?? ''));
        $status = sanitize_key((string) wp_unslash($_POST['status'] ?? 'draft'));

        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/publisher-articles/update', array(
            'draft_id' => $draft_id,
            'title' => $title,
            'excerpt' => $excerpt,
            'body_html' => $body_html,
            'status' => $status !== '' ? $status : 'edited',
        ), 60);

        if (is_wp_error($broker_result)) {
            wp_send_json_error($this->aysa_humanize_broker_error($broker_result, 'Could not save draft.'));
            return;
        }

        $draft = isset($broker_result['draft']) && is_array($broker_result['draft']) ? $broker_result['draft'] : array();
        $project_id = $this->aysa_resolve_project_id_with_local_fallback(intval(wp_unslash($_POST['project_id'] ?? 0)));
        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_adverlink_update_publisher_article_draft',
            'offpage_update_publisher_article_draft_' . $draft_id,
            array(
                'module' => 'offpage',
                'draft_id' => $draft_id,
                'status' => $status !== '' ? $status : 'edited',
            ),
            8,
            'wp_ajax/aysa_adverlink_update_publisher_article_draft'
        );
        wp_send_json_success(array('draft' => $draft));
    }

    public function handle_adverlink_approve_publisher_article_draft() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $draft_id = intval(wp_unslash($_POST['draft_id'] ?? 0));
        if ($draft_id <= 0) {
            wp_send_json_error('Invalid draft_id');
            return;
        }

        $project_id = $this->aysa_resolve_project_id_with_local_fallback(intval(wp_unslash($_POST['project_id'] ?? 0)));
        $reference = 'offpage_approve_publisher_article_draft_' . $draft_id;
        if ($this->aysa_wallet_usage_reference_exists($project_id, $reference) || $this->aysa_agent8_is_publisher_draft_approved($project_id, $draft_id)) {
            $this->aysa_agent8_mark_publisher_draft_approved($project_id, $draft_id);
            wp_send_json_success(array(
                'draft' => array('id' => $draft_id, 'status' => 'approved'),
                'already_approved' => true,
            ));
            return;
        }

        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/publisher-articles/approve', array(
            'draft_id' => $draft_id,
        ), 60);

        if (is_wp_error($broker_result)) {
            wp_send_json_error($this->aysa_humanize_broker_error($broker_result, 'Could not approve draft.'));
            return;
        }

        $draft = isset($broker_result['draft']) && is_array($broker_result['draft']) ? $broker_result['draft'] : array();
        $this->aysa_agent8_mark_publisher_draft_approved($project_id, $draft_id, $draft);
        if (!$this->aysa_wallet_usage_reference_exists($project_id, $reference)) {
            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_adverlink_approve_publisher_article_draft',
                $reference,
                array(
                    'module' => 'offpage',
                    'draft_id' => $draft_id,
                ),
                8,
                'wp_ajax/aysa_adverlink_approve_publisher_article_draft'
            );
        }
        wp_send_json_success(array('draft' => $draft));
    }

    private function aysa_create_publisher_article_draft_for_order($project_id, array $order) {
        $service_type = sanitize_key((string) ($order['service_type'] ?? ''));
        if ($service_type !== 'seo_article') {
            return new WP_Error('publisher_draft_unsupported', 'Draft generation is available only for SEO article orders.');
        }

        $existing_draft_id = intval($order['publisher_article_draft_id'] ?? 0);
        if ($existing_draft_id > 0) {
            $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/publisher-articles/get', array(
                'draft_id' => $existing_draft_id,
            ), 60);
            if (!is_wp_error($broker_result) && isset($broker_result['draft']) && is_array($broker_result['draft'])) {
                return array(
                    'draft' => $broker_result['draft'],
                    'already_exists' => true,
                );
            }
        }

        $project_id = intval($project_id);
        $payload = $this->aysa_build_publisher_article_draft_payload($project_id, $order);
        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/publisher-articles/draft/create', $payload, 120);
        if (is_wp_error($broker_result)) {
            $recovered = $this->aysa_find_publisher_article_draft_by_order_ref(
                $project_id,
                sanitize_text_field((string) ($order['order_ref'] ?? ''))
            );
            if (!is_wp_error($recovered) && isset($recovered['draft']) && is_array($recovered['draft'])) {
                return array(
                    'draft' => $recovered['draft'],
                    'already_exists' => false,
                    'recovered_after_error' => true,
                );
            }
            return $broker_result;
        }

        $draft = isset($broker_result['draft']) && is_array($broker_result['draft']) ? $broker_result['draft'] : array();
        $draft_id = intval($draft['id'] ?? 0);
        if ($draft_id > 0) {
            $orders = $this->aysa_get_adverlink_orders($project_id);
            $current_ref = sanitize_text_field((string) ($order['order_ref'] ?? ''));
            foreach ($orders as &$row) {
                if (!is_array($row)) {
                    continue;
                }
                if (sanitize_text_field((string) ($row['order_ref'] ?? '')) !== $current_ref) {
                    continue;
                }
                $row['publisher_article_draft_id'] = $draft_id;
                $row['publisher_article_draft_status'] = sanitize_key((string) ($draft['status'] ?? 'draft'));
                $row['publisher_article_draft_updated_at'] = sanitize_text_field((string) ($draft['updated_at'] ?? current_time('mysql')));
                break;
            }
            unset($row);
            $this->aysa_save_adverlink_orders($project_id, $orders);
        }

        return array(
            'draft' => $draft,
            'already_exists' => false,
        );
    }

    private function aysa_find_publisher_article_draft_by_order_ref($project_id, $order_ref) {
        $project_id = intval($project_id);
        $order_ref = sanitize_text_field((string) $order_ref);
        if ($project_id <= 0 || $order_ref === '') {
            return new WP_Error('publisher_draft_lookup_invalid', 'Invalid project_id or order_ref.');
        }

        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/publisher-articles/list', array(
            'project_id' => $project_id,
            'status' => 'draft',
            'order_ref' => $order_ref,
            'limit' => 5,
        ), 60);
        if (is_wp_error($broker_result)) {
            return $broker_result;
        }

        $items = isset($broker_result['items']) && is_array($broker_result['items']) ? $broker_result['items'] : array();
        foreach ($items as $item) {
            if (!is_array($item)) {
                continue;
            }
            if (sanitize_text_field((string) ($item['order_ref'] ?? '')) !== $order_ref) {
                continue;
            }
            return array('draft' => $item);
        }

        return new WP_Error('publisher_draft_not_found', 'Publisher article draft was not found after create.');
    }

    private function aysa_build_publisher_article_draft_payload($project_id, array $order) {
        $setup_user_id = function_exists('get_current_user_id') ? intval(get_current_user_id()) : 0;
        $setup = $this->aysa_get_local_setup_state_for_user($setup_user_id);
        $general = isset($setup['general']) && is_array($setup['general']) ? $setup['general'] : array();

        $keyword = sanitize_text_field((string) ($order['keyword'] ?? ''));
        $article_keywords = isset($order['article_keywords']) && is_array($order['article_keywords']) ? $order['article_keywords'] : array();
        $language = sanitize_key((string) ($order['article_language'] ?? 'ro'));
        if (!in_array($language, array('ro', 'en', 'bg'), true)) {
            $language = 'ro';
        }

        $business_name = sanitize_text_field((string) ($general['company_name'] ?? get_bloginfo('name')));
        $business_type = sanitize_text_field((string) ($general['business_type'] ?? $general['industry'] ?? ''));
        $business_description = sanitize_textarea_field((string) ($general['business_description'] ?? $general['company_description'] ?? $general['about_business'] ?? ''));

        $products_services = array();
        foreach (array('products_services', 'main_services', 'services', 'products') as $key) {
            if (!isset($general[$key])) {
                continue;
            }
            if (is_array($general[$key])) {
                foreach ($general[$key] as $item) {
                    $text = sanitize_text_field((string) $item);
                    if ($text !== '') {
                        $products_services[] = $text;
                    }
                }
            } else {
                $chunks = preg_split('/[,;\n]+/', (string) $general[$key]) ?: array();
                foreach ($chunks as $item) {
                    $text = sanitize_text_field((string) $item);
                    if ($text !== '') {
                        $products_services[] = $text;
                    }
                }
            }
        }
        $products_services = array_values(array_unique(array_filter($products_services)));

        $internal_links = array();
        $targets = $this->aysa_collect_wp_primary_keyword_targets($keyword, 6);
        foreach ($targets as $target) {
            if (!is_array($target)) {
                continue;
            }
            $href = esc_url_raw((string) ($target['url'] ?? ''));
            $anchor = sanitize_text_field((string) ($target['keyword'] ?? ''));
            $title = sanitize_text_field((string) ($target['title'] ?? $anchor));
            if ($href === '' || $anchor === '') {
                continue;
            }
            $internal_links[] = array(
                'href' => $href,
                'anchor' => $anchor,
                'title' => $title !== '' ? $title : $anchor,
            );
            if (count($internal_links) >= 2) {
                break;
            }
        }

        $article_topic = sanitize_text_field((string) ($order['article_topic'] ?? ''));
        if ($article_topic === '') {
            $article_topic = $business_name . ' - ' . $keyword;
        }

        $keyword_cluster = array_values(array_unique(array_filter(array_merge(array($keyword), array_map('sanitize_text_field', $article_keywords)))));
        $notes = sanitize_textarea_field((string) ($order['article_brief'] ?? ''));

        return array(
            'project_id' => intval($project_id),
            'order_ref' => sanitize_text_field((string) ($order['order_ref'] ?? '')),
            'order_id' => sanitize_text_field((string) ($order['order_id'] ?? '')),
            'portal_id' => sanitize_text_field((string) ($order['portal_id'] ?? '')),
            'portal_domain' => sanitize_text_field((string) ($order['portal_domain'] ?? '')),
            'service_type' => 'seo_article',
            'language' => $language,
            'primary_keyword' => $keyword,
            'secondary_keywords' => array_values(array_unique(array_filter(array_map('sanitize_text_field', $article_keywords)))),
            'keyword_cluster' => $keyword_cluster,
            'business_name' => $business_name,
            'business_type' => $business_type,
            'business_description' => $business_description,
            'products_services' => $products_services,
            'target_url' => esc_url_raw((string) ($order['target_url'] ?? home_url('/'))),
            'internal_links' => $internal_links,
            'article_goal' => 'Create a publisher-ready article that can support the target page with natural editorial SEO value.',
            'article_angle' => $article_topic,
            'tone' => 'professional editorial',
            'word_count' => max(700, intval($order['article_word_count'] ?? 900)),
            'image_count' => 2,
            'notes_for_writer' => $notes,
            'publisher_context' => sanitize_text_field((string) ($order['portal_domain'] ?? 'specialty publisher')),
        );
    }

    private function aysa_sync_publisher_article_drafts_from_orders($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return;
        }

        $orders = $this->aysa_get_adverlink_orders($project_id);
        if (!is_array($orders) || empty($orders)) {
            return;
        }

        foreach ($orders as $order) {
            if (!is_array($order)) {
                continue;
            }
            if (sanitize_key((string) ($order['service_type'] ?? '')) !== 'seo_article') {
                continue;
            }

            $draft_id = intval($order['publisher_article_draft_id'] ?? 0);
            if ($draft_id <= 0) {
                continue;
            }

            $status = strtoupper(trim((string) ($order['status'] ?? '')));
            $published_url = '';

            $source = null;
            if (isset($order['raw_track']) && is_array($order['raw_track'])) {
                $source = $order['raw_track'];
            } elseif (isset($order['raw_response']) && is_array($order['raw_response'])) {
                $source = $order['raw_response'];
            }
            if (is_array($source)) {
                $published_url = esc_url_raw((string) $this->aysa_find_first_string_by_keys_recursive($source, array(
                    'published_url', 'publishedUrl', 'insertion_url', 'insertionUrl',
                    'article_url', 'articleUrl', 'placement_url', 'placementUrl',
                    'source_url', 'sourceUrl', 'published_on_url', 'publishedOnUrl'
                )));
            }

            if (!in_array($status, array('COMPLETED', 'PUBLISHED', 'LIVE'), true) || $published_url === '') {
                continue;
            }

            $this->aysa_broker_request('POST', '/adverlink/v1/publisher-articles/update', array(
                'draft_id' => $draft_id,
                'status' => 'published',
                'published_url' => $published_url,
            ), 60);
        }
    }

    private function aysa_extract_assoc_value($row, $keys, $default = '') {
        foreach ((array) $keys as $key) {
            if (isset($row[$key]) && $row[$key] !== null && $row[$key] !== '') {
                return $row[$key];
            }
        }
        return $default;
    }

    private function aysa_build_entity_url($entity_type, $entity_id, $fallback_url = '') {
        $entity_id = intval($entity_id);
        if (!empty($fallback_url)) {
            $url = esc_url_raw((string) $fallback_url);
            if (!empty($url)) {
                return $url;
            }
        }
        if ($entity_id <= 0) {
            return '';
        }

        $type = sanitize_key((string) $entity_type);
        if (in_array($type, array('post', 'page', 'product', 'article'), true)) {
            $post = get_post($entity_id);
            if ($post && $post->post_status === 'publish') {
                return (string) get_permalink($entity_id);
            }
        }

        if (in_array($type, array('category', 'product_cat', 'tag', 'product_tag', 'term'), true)) {
            $taxonomy_candidates = array();
            if ($type === 'category') {
                $taxonomy_candidates = array('category', 'product_cat');
            } elseif ($type === 'tag') {
                $taxonomy_candidates = array('post_tag', 'product_tag');
            } elseif ($type === 'term') {
                $taxonomy_candidates = array('category', 'product_cat', 'post_tag', 'product_tag');
            } else {
                $taxonomy_candidates = array($type);
            }

            foreach ($taxonomy_candidates as $taxonomy) {
                $term = get_term($entity_id, $taxonomy);
                if ($term && !is_wp_error($term)) {
                    $term_link = get_term_link($term);
                    if (!is_wp_error($term_link)) {
                        return (string) $term_link;
                    }
                }
            }
        }

        $post = get_post($entity_id);
        if ($post && $post->post_status === 'publish') {
            return (string) get_permalink($entity_id);
        }

        return '';
    }

    private function aysa_build_entity_title($entity_type, $entity_id, $fallback_title = '') {
        $title = sanitize_text_field((string) $fallback_title);
        if ($title !== '') {
            return $title;
        }

        $entity_id = intval($entity_id);
        if ($entity_id <= 0) {
            return '';
        }

        $type = sanitize_key((string) $entity_type);
        if (in_array($type, array('post', 'page', 'product', 'article'), true)) {
            $post = get_post($entity_id);
            if ($post) {
                return sanitize_text_field((string) $post->post_title);
            }
        }

        if (in_array($type, array('category', 'product_cat', 'tag', 'product_tag', 'term'), true)) {
            $taxonomy_candidates = array();
            if ($type === 'category') {
                $taxonomy_candidates = array('category', 'product_cat');
            } elseif ($type === 'tag') {
                $taxonomy_candidates = array('post_tag', 'product_tag');
            } elseif ($type === 'term') {
                $taxonomy_candidates = array('category', 'product_cat', 'post_tag', 'product_tag');
            } else {
                $taxonomy_candidates = array($type);
            }

            foreach ($taxonomy_candidates as $taxonomy) {
                $term = get_term($entity_id, $taxonomy);
                if ($term && !is_wp_error($term)) {
                    return sanitize_text_field((string) $term->name);
                }
            }
        }

        $post = get_post($entity_id);
        if ($post) {
            return sanitize_text_field((string) $post->post_title);
        }

        return '';
    }

    private function aysa_collect_wp_primary_keyword_targets($query, $limit = 25) {
        global $wpdb;
        $results = array();
        $limit = max(1, min(100, intval($limit)));
        $like = '%' . $wpdb->esc_like($query) . '%';

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $post_rows = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT p.ID AS entity_id, p.post_type AS entity_type, p.post_title AS entity_title, m.meta_value AS keyword_value
                 FROM {$wpdb->postmeta} m
                 INNER JOIN {$wpdb->posts} p ON p.ID = m.post_id
                 WHERE m.meta_key IN ('_aysa_primary_keyword','aysa_primary_keyword')
                   AND p.post_status = 'publish'
                   AND p.post_type IN ('post','page','product')
                   AND m.meta_value LIKE %s
                 ORDER BY p.post_modified_gmt DESC
                 LIMIT %d",
                $like,
                $limit
            ),
            ARRAY_A
        );

        if (is_array($post_rows)) {
            foreach ($post_rows as $row) {
                $entity_id = intval($row['entity_id'] ?? 0);
                if ($entity_id <= 0) {
                    continue;
                }
                $entity_type = sanitize_key((string) ($row['entity_type'] ?? 'post'));
                $url = get_permalink($entity_id);
                if (!$url) {
                    continue;
                }
                $keyword = sanitize_text_field((string) ($row['keyword_value'] ?? ''));
                if ($keyword === '') {
                    continue;
                }
                $results[] = array(
                    'keyword' => $keyword,
                    'entity_id' => $entity_id,
                    'entity_type' => $entity_type,
                    'title' => sanitize_text_field((string) ($row['entity_title'] ?? '')),
                    'url' => esc_url_raw((string) $url),
                    'source' => 'wp_meta'
                );
            }
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $term_rows = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT t.term_id AS entity_id, tt.taxonomy AS entity_type, t.name AS entity_title, tm.meta_value AS keyword_value
                 FROM {$wpdb->termmeta} tm
                 INNER JOIN {$wpdb->term_taxonomy} tt ON tt.term_id = tm.term_id
                 INNER JOIN {$wpdb->terms} t ON t.term_id = tm.term_id
                 WHERE tm.meta_key IN ('_aysa_primary_keyword','aysa_primary_keyword')
                   AND tm.meta_value LIKE %s
                 ORDER BY t.term_id DESC
                 LIMIT %d",
                $like,
                $limit
            ),
            ARRAY_A
        );

        if (is_array($term_rows)) {
            foreach ($term_rows as $row) {
                $entity_id = intval($row['entity_id'] ?? 0);
                if ($entity_id <= 0) {
                    continue;
                }
                $entity_type = sanitize_key((string) ($row['entity_type'] ?? 'term'));
                $term_link = get_term_link($entity_id, $entity_type);
                if (is_wp_error($term_link) || empty($term_link)) {
                    continue;
                }
                $keyword = sanitize_text_field((string) ($row['keyword_value'] ?? ''));
                if ($keyword === '') {
                    continue;
                }
                $results[] = array(
                    'keyword' => $keyword,
                    'entity_id' => $entity_id,
                    'entity_type' => $entity_type,
                    'title' => sanitize_text_field((string) ($row['entity_title'] ?? '')),
                    'url' => esc_url_raw((string) $term_link),
                    'source' => 'wp_meta'
                );
            }
        }

        return $results;
    }

    public function handle_adverlink_search_link_targets() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_project_id_with_local_fallback(intval(wp_unslash($_POST['project_id'] ?? 0)));
        $query = sanitize_text_field((string) wp_unslash($_POST['q'] ?? ''));
        $limit = max(1, min(100, intval(wp_unslash($_POST['limit'] ?? 25))));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project ID.');
            return;
        }
        $query_len = function_exists('mb_strlen') ? mb_strlen($query, 'UTF-8') : strlen($query);
        if ($query === '' || $query_len < 2) {
            wp_send_json_success(array('items' => array()));
            return;
        }

        $items = array();

        if (class_exists('Aysa_Auth_Manager')) {
            try {
                $auth_manager = Aysa_Auth_Manager::get_instance();
                if ($auth_manager && $auth_manager->is_authenticated()) {
                    $assoc_resp = $auth_manager->make_authenticated_request(
                        'api/Association?projectId=' . $project_id . '&pageSize=10000',
                        'GET'
                    );
                    $assoc_rows = $this->normalize_aysa_collection_response($assoc_resp);
                    foreach ($assoc_rows as $assoc) {
                        if (!is_array($assoc)) {
                            continue;
                        }

                        $keyword = sanitize_text_field((string) $this->aysa_extract_assoc_value($assoc, array('keyword', 'Keyword', 'primary_keyword'), ''));
                        if ($keyword === '') {
                            continue;
                        }

                        $keyword_lc = function_exists('mb_strtolower') ? mb_strtolower($keyword, 'UTF-8') : strtolower($keyword);
                        $query_lc = function_exists('mb_strtolower') ? mb_strtolower($query, 'UTF-8') : strtolower($query);
                        if (strpos($keyword_lc, $query_lc) === false) {
                            continue;
                        }

                        $is_main_raw = $this->aysa_extract_assoc_value($assoc, array('isMain', 'is_main', 'IsMain'), true);
                        $is_main = is_bool($is_main_raw)
                            ? $is_main_raw
                            : in_array(strtolower((string) $is_main_raw), array('1', 'true', 'yes'), true);
                        if (!$is_main) {
                            continue;
                        }

                        $entity_id = intval($this->aysa_extract_assoc_value($assoc, array('idIntern', 'id_intern', 'entityId', 'entity_id'), 0));
                        $entity_type = sanitize_key((string) $this->aysa_extract_assoc_value($assoc, array('associatedOn', 'entity_type', 'AssociatedOn'), ''));
                        $raw_link = (string) $this->aysa_extract_assoc_value($assoc, array('link', 'url', 'matched_page_url'), '');
                        $raw_title = (string) $this->aysa_extract_assoc_value($assoc, array('value', 'title', 'entityTitle'), '');

                        $resolved_url = $this->aysa_build_entity_url($entity_type, $entity_id, $raw_link);
                        if ($resolved_url === '') {
                            continue;
                        }

                        $items[] = array(
                            'keyword' => $keyword,
                            'entity_id' => $entity_id,
                            'entity_type' => $entity_type,
                            'title' => $this->aysa_build_entity_title($entity_type, $entity_id, $raw_title),
                            'url' => $resolved_url,
                            'source' => 'associations_api'
                        );
                    }
                }
            } catch (Exception $e) {
                aysa_debug_log('[AYSA Adverlink] link target search API error: ' . $e->getMessage());
            }
        }

        $items = array_merge($items, $this->aysa_collect_wp_primary_keyword_targets($query, $limit));

        $dedupe = array();
        $normalized = array();
        $query_lc = function_exists('mb_strtolower') ? mb_strtolower($query, 'UTF-8') : strtolower($query);
        foreach ($items as $item) {
            $keyword = sanitize_text_field((string) ($item['keyword'] ?? ''));
            $url = esc_url_raw((string) ($item['url'] ?? ''));
            if ($keyword === '' || $url === '') {
                continue;
            }
            $entity_type = sanitize_key((string) ($item['entity_type'] ?? ''));
            $entity_id = intval($item['entity_id'] ?? 0);
            $key = md5($keyword . '|' . $url . '|' . $entity_type . '|' . $entity_id);
            if (isset($dedupe[$key])) {
                continue;
            }
            $dedupe[$key] = true;

            $kw_lc = function_exists('mb_strtolower') ? mb_strtolower($keyword, 'UTF-8') : strtolower($keyword);
            $rank = 3;
            if ($kw_lc === $query_lc) {
                $rank = 0;
            } elseif (strpos($kw_lc, $query_lc) === 0) {
                $rank = 1;
            } elseif (strpos($kw_lc, $query_lc) !== false) {
                $rank = 2;
            }

            $normalized[] = array(
                'keyword' => $keyword,
                'entity_id' => $entity_id,
                'entity_type' => $entity_type,
                'title' => sanitize_text_field((string) ($item['title'] ?? '')),
                'url' => $url,
                'source' => sanitize_key((string) ($item['source'] ?? '')),
                '_rank' => $rank
            );
        }

        usort($normalized, function ($a, $b) {
            if (($a['_rank'] ?? 99) !== ($b['_rank'] ?? 99)) {
                return intval($a['_rank'] ?? 99) - intval($b['_rank'] ?? 99);
            }
            $ak = strlen((string) ($a['keyword'] ?? ''));
            $bk = strlen((string) ($b['keyword'] ?? ''));
            if ($ak !== $bk) {
                return $ak - $bk;
            }
            return strcmp((string) ($a['title'] ?? ''), (string) ($b['title'] ?? ''));
        });

        $normalized = array_slice($normalized, 0, $limit);
        foreach ($normalized as &$item) {
            unset($item['_rank']);
        }
        unset($item);

        wp_send_json_success(array(
            'items' => $normalized
        ));
    }

    public function handle_adverlink_credit_balance() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project ID.');
            return;
        }

        $actual_credits = $this->aysa_get_project_credit_balance($project_id, true);
        $orders = $this->aysa_get_adverlink_orders($project_id);
        $reserved_credits = $this->aysa_reconcile_reserved_credits($project_id, $orders);
        $available_credits = max(0, $actual_credits - $reserved_credits);
        $wallet_snapshot = array();
        $wallet_manager = $this->aysa_get_wallet_manager_instance();
        if ($wallet_manager && method_exists($wallet_manager, 'get_wallet_snapshot')) {
            $wallet_snapshot = $wallet_manager->get_wallet_snapshot($project_id, array(
                'allow_remote' => false,
            ));
        }

        $included_links_total = intval($wallet_snapshot['included_links_total'] ?? 0);
        $included_links_used = intval($wallet_snapshot['included_links_used'] ?? 0);
        $included_links_period_starts_at = sanitize_text_field((string) ($wallet_snapshot['included_links_period_starts_at'] ?? ''));
        $included_links_period_ends_at = sanitize_text_field((string) ($wallet_snapshot['included_links_period_ends_at'] ?? ''));
        foreach ($orders as $order) {
            if (!is_array($order) || empty($order['included_link_applied'])) {
                continue;
            }
            $included_links_used++;
        }

        if ($included_links_total > 0) {
            $included_links_used = min($included_links_used, $included_links_total);
        }
        $included_links_remaining = max(0, $included_links_total - $included_links_used);

        wp_send_json_success(array(
            'project_id' => $project_id,
            'actual_credits' => $actual_credits,
            'reserved_credits' => $reserved_credits,
            'available_credits' => $available_credits,
            'included_links_total' => $included_links_total,
            'included_links_used' => $included_links_used,
            'included_links_remaining' => $included_links_remaining,
            'included_links_period_starts_at' => $included_links_period_starts_at,
            'included_links_period_ends_at' => $included_links_period_ends_at,
            'latest_usage_amount_cents' => intval($wallet_snapshot['latest_usage_amount_cents'] ?? 0),
            'latest_usage_action_key' => sanitize_key((string) ($wallet_snapshot['latest_usage_action_key'] ?? '')),
            'latest_usage_reference' => sanitize_text_field((string) ($wallet_snapshot['latest_usage_reference'] ?? '')),
            'latest_usage_at' => sanitize_text_field((string) ($wallet_snapshot['latest_usage_at'] ?? '')),
        ));
    }

    public function handle_adverlink_marketplace_diagnostics() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error(array(
                'code' => 'invalid_nonce',
                'message' => 'Invalid nonce.',
            ));
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array(
                'code' => 'insufficient_permissions',
                'message' => 'Insufficient permissions.',
            ));
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error(array(
                'code' => 'invalid_project_id',
                'message' => 'Invalid project ID.',
            ));
            return;
        }
        $creds = $this->aysa_get_adverlink_credentials();
        $credential_type = $this->aysa_detect_adverlink_credential_type($creds['api_key'] ?? '');
        $credentials_check = $this->aysa_validate_adverlink_marketplace_credentials($creds);

        $response = array(
            'project_id' => $project_id,
            'credential_type' => $credential_type,
            'has_api_key' => trim((string) ($creds['api_key'] ?? '')) !== '',
            'has_advertiser_org_id' => trim((string) ($creds['org_id'] ?? '')) !== '',
        );

        if (is_wp_error($credentials_check)) {
            wp_send_json_error(array_merge($response, array(
                'code' => $credentials_check->get_error_code(),
                'message' => $credentials_check->get_error_message(),
            )));
            return;
        }

        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/public/diagnostics', array(
            'project_id' => $project_id,
            'api_key' => $creds['api_key'],
            'advertiser_org_id' => $creds['org_id'],
        ), 25);

        if (is_wp_error($broker_result)) {
            wp_send_json_error(array_merge($response, array(
                'code' => 'broker_request_failed',
                'message' => $this->aysa_humanize_broker_error($broker_result, 'Could not reach marketplace diagnostics.'),
            )));
            return;
        }

        $payload = is_array($broker_result) ? $broker_result : array();
        $ok = !empty($payload['public_marketplace_ready']);
        $response = array_merge($response, array(
            'provider_status' => intval($payload['provider_status'] ?? 0),
            'upstream_base_url' => sanitize_text_field((string) ($payload['upstream_base_url'] ?? '')),
            'test_scope' => !empty($payload['test_scope']),
            'marketplace_meta' => is_array($payload['marketplace_meta'] ?? null) ? $payload['marketplace_meta'] : array(),
            'public_marketplace_ready' => $ok,
            'probe' => is_array($payload['probe'] ?? null) ? $payload['probe'] : array(),
            'message' => $ok
                ? 'Marketplace diagnostics passed.'
                : sanitize_text_field((string) (($payload['error']['message'] ?? '') ?: 'Marketplace diagnostics failed.')),
        ));

        if (!$ok) {
            wp_send_json_error(array_merge($response, array(
                'code' => sanitize_text_field((string) ($payload['error']['code'] ?? 'marketplace_not_ready')),
            )));
            return;
        }

        wp_send_json_success($response);
    }

    public function handle_adverlink_exchange_list_portals() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project ID.');
            return;
        }

        $requester = $this->aysa_get_link_exchange_profile($project_id);
        $state_action = isset($_POST['state_action'])
            ? sanitize_key(wp_unslash($_POST['state_action']))
            : sanitize_key(wp_unslash($_POST['action_name'] ?? ''));
        $payload = array(
            'project_id' => $project_id,
            'per_page' => max(1, min(100, intval(wp_unslash($_POST['per_page'] ?? 100)))),
            'requester_site_url' => $requester['site_url'],
            'requester_site_domain' => $requester['site_domain'],
            'requester_website_type' => $requester['website_type'],
            'requester_industry' => $requester['industry'],
            'requester_language' => $requester['language'],
            'requester_country' => $requester['country'],
            'requester_topics' => array_values($requester['topics']),
            'requester_authority_value' => intval($requester['authority_value'] ?? 0),
        );

        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/portals', $payload, 45);
        if (is_wp_error($broker_result)) {
            wp_send_json_error(array(
                'code' => 'broker_error',
                'message' => $this->aysa_humanize_broker_error($broker_result, 'Could not load eligible exchange portals.'),
            ));
            return;
        }

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_link_exchange_match_sites',
            'offpage_exchange_list_portals_' . $project_id,
            array(
                'module' => 'offpage',
                'source' => 'aysa_exchange_network',
            ),
            4,
            'wp_ajax/aysa_adverlink_exchange_list_portals'
        );

        $items = isset($broker_result['aysa_exchange_sites']) && is_array($broker_result['aysa_exchange_sites'])
            ? $broker_result['aysa_exchange_sites']
            : (isset($broker_result['items']) && is_array($broker_result['items']) ? $broker_result['items'] : array());
        wp_send_json_success(array(
            'items' => $items,
            'aysa_exchange_sites' => $items,
            'requester_profile' => isset($broker_result['requester_profile']) && is_array($broker_result['requester_profile']) ? $broker_result['requester_profile'] : $requester,
        ));
    }

    public function handle_adverlink_exchange_create_request() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project ID.');
            return;
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $portal_json = (string) wp_unslash($_POST['counterparty_portal_json'] ?? '');
        $portal = json_decode(wp_unslash($portal_json), true);
        if (!is_array($portal) || empty($portal)) {
            wp_send_json_error('Counterparty portal is required.');
            return;
        }

        $requester = $this->aysa_get_link_exchange_profile($project_id);
        $payload = array(
            'project_id' => $project_id,
            'requester_site_url' => $requester['site_url'],
            'requester_site_domain' => $requester['site_domain'],
            'requester_website_type' => $requester['website_type'],
            'requester_industry' => $requester['industry'],
            'requester_language' => $requester['language'],
            'requester_country' => $requester['country'],
            'requester_topics' => array_values($requester['topics']),
            'requester_authority_value' => intval($requester['authority_value'] ?? 0),
            'counterparty_portal' => $portal,
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
            'requester_target_url' => esc_url_raw(trim((string) wp_unslash($_POST['requester_target_url'] ?? ''))),
            'requester_anchor_text' => sanitize_text_field((string) wp_unslash($_POST['requester_anchor_text'] ?? '')),
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
            'counterparty_target_url' => esc_url_raw(trim((string) wp_unslash($_POST['counterparty_target_url'] ?? ''))),
            'counterparty_anchor_text' => sanitize_text_field((string) wp_unslash($_POST['counterparty_anchor_text'] ?? '')),
            'requester_notes' => sanitize_textarea_field((string) wp_unslash($_POST['requester_notes'] ?? '')),
        );

        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/create', $payload, 45);
        if (is_wp_error($broker_result)) {
            wp_send_json_error(array(
                'code' => 'broker_error',
                'message' => $this->aysa_humanize_broker_error($broker_result, 'Could not create exchange request.'),
            ));
            return;
        }

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_link_exchange_create_request',
            'offpage_exchange_create_request_' . md5((string) ($payload['requester_target_url'] ?? '') . '|' . (string) ($payload['counterparty_target_url'] ?? '')),
            array(
                'module' => 'offpage',
                'source' => 'aysa_exchange_network',
            ),
            12,
            'wp_ajax/aysa_adverlink_exchange_create_request'
        );

        wp_send_json_success(array(
            'request' => isset($broker_result['request']) && is_array($broker_result['request']) ? $broker_result['request'] : array(),
            'deduplicated' => !empty($broker_result['deduplicated']),
        ));
    }

    public function handle_adverlink_exchange_list_requests() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project ID.');
            return;
        }

        $requester = $this->aysa_get_link_exchange_profile($project_id);
        $payload = array(
            'project_id' => $project_id,
            'current_site_domain' => $requester['site_domain'],
            'direction' => sanitize_text_field((string) wp_unslash($_POST['direction'] ?? 'all')),
            'status' => sanitize_text_field((string) wp_unslash($_POST['status'] ?? '')),
            'limit' => max(1, min(200, intval(wp_unslash($_POST['limit'] ?? 50)))),
        );

        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/list', $payload, 30);
        if (is_wp_error($broker_result)) {
            wp_send_json_error(array(
                'code' => 'broker_error',
                'message' => $this->aysa_humanize_broker_error($broker_result, 'Could not load exchange requests.'),
            ));
            return;
        }

        wp_send_json_success(array(
            'items' => isset($broker_result['items']) && is_array($broker_result['items']) ? $broker_result['items'] : array(),
        ));
    }

    public function handle_adverlink_exchange_get_request() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project ID.');
            return;
        }

        $requester = $this->aysa_get_link_exchange_profile($project_id);
        $payload = array(
            'project_id' => $project_id,
            'current_site_domain' => $requester['site_domain'],
            'request_id' => intval(wp_unslash($_POST['request_id'] ?? 0)),
            'exchange_ref' => sanitize_text_field((string) wp_unslash($_POST['exchange_ref'] ?? '')),
        );

        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/get', $payload, 30);
        if (is_wp_error($broker_result)) {
            wp_send_json_error(array(
                'code' => 'broker_error',
                'message' => $this->aysa_humanize_broker_error($broker_result, 'Could not load exchange request.'),
            ));
            return;
        }

        wp_send_json_success(array(
            'request' => isset($broker_result['request']) && is_array($broker_result['request']) ? $broker_result['request'] : array(),
        ));
    }

    public function handle_adverlink_exchange_list_activation_candidates() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $limit = max(1, min(100, intval(wp_unslash($_POST['limit'] ?? 30))));
        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project ID.');
            return;
        }
        $requester = $this->aysa_get_link_exchange_profile($project_id);
        $exchange_ref = sanitize_text_field((string) wp_unslash($_POST['exchange_ref'] ?? ''));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $target_url = esc_url_raw(trim((string) wp_unslash($_POST['target_url'] ?? '')));
        $anchor_text = sanitize_text_field((string) wp_unslash($_POST['anchor_text'] ?? ''));
        if ($exchange_ref !== '' && $project_id > 0) {
            $request_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/get', array(
                'project_id' => $project_id,
                'current_site_domain' => $requester['site_domain'],
                'exchange_ref' => $exchange_ref,
            ), 30);
            if (!is_wp_error($request_result) && isset($request_result['request']) && is_array($request_result['request'])) {
                $request = $request_result['request'];
                $role = (string) ($request['viewer_role'] ?? '');
                if ($target_url === '') {
                    $target_url = $role === 'requester'
                        ? esc_url_raw((string) ($request['counterparty_target_url'] ?? ''))
                        : esc_url_raw((string) ($request['requester_target_url'] ?? ''));
                }
                if ($anchor_text === '') {
                    $anchor_text = $role === 'requester'
                        ? sanitize_text_field((string) ($request['counterparty_anchor_text'] ?? ''))
                        : sanitize_text_field((string) ($request['requester_anchor_text'] ?? ''));
                }
            }
        }
        $post_types = array('post', 'page');
        if (post_type_exists('product')) {
            $post_types[] = 'product';
        }

        $posts = get_posts(array(
            'post_type' => $post_types,
            'post_status' => 'publish',
            'posts_per_page' => $limit,
            'orderby' => 'modified',
            'order' => 'DESC',
            'suppress_filters' => false,
        ));

        $items = array();
        foreach ($posts as $post) {
            if (!($post instanceof WP_Post)) {
                continue;
            }
            $semantic = $this->aysa_exchange_score_source_candidate($post, $target_url, $anchor_text);
            $items[] = array(
                'id' => intval($post->ID),
                'title' => get_the_title($post),
                'url' => get_permalink($post),
                'post_type' => $post->post_type,
                'excerpt' => wp_trim_words(wp_strip_all_tags((string) ($post->post_excerpt ?: $post->post_content)), 24, ''),
                'semantic_score' => intval($semantic['score'] ?? 0),
                'semantic_fit' => !empty($semantic['eligible']),
                'semantic_reasons' => isset($semantic['reasons']) && is_array($semantic['reasons']) ? array_values($semantic['reasons']) : array(),
            );
        }

        usort($items, static function ($left, $right) {
            $score_left = intval($left['semantic_score'] ?? 0);
            $score_right = intval($right['semantic_score'] ?? 0);
            if ($score_left !== $score_right) {
                return $score_right <=> $score_left;
            }
            return strcmp((string) ($left['title'] ?? ''), (string) ($right['title'] ?? ''));
        });

        wp_send_json_success(array(
            'items' => $items,
        ));
    }

    public function handle_adverlink_exchange_respond_request() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project ID.');
            return;
        }

        $requester = $this->aysa_get_link_exchange_profile($project_id);
        $payload = array(
            'project_id' => $project_id,
            'current_site_domain' => $requester['site_domain'],
            'request_id' => intval(wp_unslash($_POST['request_id'] ?? 0)),
            'exchange_ref' => sanitize_text_field((string) wp_unslash($_POST['exchange_ref'] ?? '')),
            'decision' => sanitize_text_field((string) wp_unslash($_POST['decision'] ?? 'accept')),
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
            'counterparty_target_url' => esc_url_raw(trim((string) wp_unslash($_POST['counterparty_target_url'] ?? ''))),
            'counterparty_anchor_text' => sanitize_text_field((string) wp_unslash($_POST['counterparty_anchor_text'] ?? '')),
            'counterparty_notes' => sanitize_textarea_field((string) wp_unslash($_POST['counterparty_notes'] ?? '')),
        );

        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/respond', $payload, 30);
        if (is_wp_error($broker_result)) {
            wp_send_json_error(array(
                'code' => 'broker_error',
                'message' => $this->aysa_humanize_broker_error($broker_result, 'Could not update exchange request.'),
            ));
            return;
        }

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_adverlink_exchange_respond_request',
            'offpage_exchange_respond_request_' . md5((string) ($payload['exchange_ref'] ?? '') . '|' . (string) ($payload['decision'] ?? '')),
            array(
                'module' => 'offpage',
                'decision' => (string) ($payload['decision'] ?? ''),
            ),
            8,
            'wp_ajax/aysa_adverlink_exchange_respond_request'
        );

        wp_send_json_success(array(
            'request' => isset($broker_result['request']) && is_array($broker_result['request']) ? $broker_result['request'] : array(),
        ));
    }

    public function handle_adverlink_exchange_activate_link() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project ID.');
            return;
        }

        $requester = $this->aysa_get_link_exchange_profile($project_id);
        $request_payload = array(
            'project_id' => $project_id,
            'current_site_domain' => $requester['site_domain'],
            'request_id' => intval(wp_unslash($_POST['request_id'] ?? 0)),
            'exchange_ref' => sanitize_text_field((string) wp_unslash($_POST['exchange_ref'] ?? '')),
        );
        $request_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/get', $request_payload, 30);
        if (is_wp_error($request_result)) {
            wp_send_json_error(array(
                'code' => 'broker_error',
                'message' => $this->aysa_humanize_broker_error($request_result, 'Could not load exchange request before activation.'),
            ));
            return;
        }

        $request = isset($request_result['request']) && is_array($request_result['request']) ? $request_result['request'] : array();
        if (empty($request)) {
            wp_send_json_error('Exchange request not found.');
            return;
        }

        if (sanitize_key((string) wp_unslash($_POST['confirmation_kind'] ?? '')) === 'link_exchange_placement_apply') {
            $confirmed_payload = array();
            foreach ($_POST as $key => $value) {
                $confirmed_payload[$key] = is_string($value) ? wp_unslash($value) : $value;
            }

            $controlled = $this->aysa_exchange_apply_confirmed_partner_box($project_id, $requester, $request, $confirmed_payload);
            if (is_wp_error($controlled)) {
                wp_send_json_error(array(
                    'code' => $controlled->get_error_code(),
                    'message' => $controlled->get_error_message(),
                ));
                return;
            }
            wp_send_json_success($controlled);
            return;
        }

        $source_post_id = intval(wp_unslash($_POST['source_post_id'] ?? 0));
        if ($source_post_id <= 0) {
            $source_post_id = $this->aysa_exchange_find_auto_source_post_id(
                esc_url_raw((string) ($request['counterparty_target_url'] ?? $request['requester_target_url'] ?? '')),
                sanitize_text_field((string) ($request['counterparty_anchor_text'] ?? $request['requester_anchor_text'] ?? ''))
            );
        }
        if ($source_post_id <= 0) {
            wp_send_json_error('Source content is required.');
            return;
        }

        $role = (string) ($request['viewer_role'] ?? '');
        $target_url = $role === 'requester'
            ? esc_url_raw((string) ($request['counterparty_target_url'] ?? ''))
            : esc_url_raw((string) ($request['requester_target_url'] ?? ''));
        $anchor_text = $role === 'requester'
            ? sanitize_text_field((string) ($request['counterparty_anchor_text'] ?? ''))
            : sanitize_text_field((string) ($request['requester_anchor_text'] ?? ''));

        if ($target_url === '' || $anchor_text === '') {
            wp_send_json_error('Target URL and anchor are missing for activation.');
            return;
        }

        $source_post = get_post($source_post_id);
        $semantic = $this->aysa_exchange_score_source_candidate($source_post, $target_url, $anchor_text);

        $activation = $this->aysa_exchange_insert_link_into_post($source_post_id, $target_url, $anchor_text, (string) ($request['exchange_ref'] ?? ''));
        if (is_wp_error($activation)) {
            wp_send_json_error(array(
                'code' => $activation->get_error_code(),
                'message' => $activation->get_error_message(),
            ));
            return;
        }

        $payload = array(
            'project_id' => $project_id,
            'current_site_domain' => $requester['site_domain'],
            'request_id' => intval($request['id'] ?? 0),
            'exchange_ref' => sanitize_text_field((string) ($request['exchange_ref'] ?? '')),
            'source_post_id' => $source_post_id,
            'source_url' => esc_url_raw((string) ($activation['source_url'] ?? '')),
            'published_url' => esc_url_raw((string) ($activation['source_url'] ?? '')),
            'target_url' => $target_url,
            'anchor_text' => $anchor_text,
            'notes' => sanitize_textarea_field((string) wp_unslash($_POST['notes'] ?? '')),
        );

        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/activate', $payload, 45);
        if (is_wp_error($broker_result)) {
            wp_send_json_error(array(
                'code' => 'broker_error',
                'message' => $this->aysa_humanize_broker_error($broker_result, 'Could not activate reciprocal link.'),
            ));
            return;
        }

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_adverlink_exchange_activate_link',
            'offpage_exchange_activate_link_' . md5((string) ($request['exchange_ref'] ?? '') . '|' . (string) $source_post_id),
            array(
                'module' => 'offpage',
                'exchange_ref' => (string) ($request['exchange_ref'] ?? ''),
                'source_post_id' => $source_post_id,
            ),
            12,
            'wp_ajax/aysa_adverlink_exchange_activate_link'
        );

        wp_send_json_success(array(
            'request' => isset($broker_result['request']) && is_array($broker_result['request']) ? $broker_result['request'] : array(),
            'activation' => $activation,
            'semantic' => $semantic,
            'placement_mode' => !empty($semantic['eligible']) ? 'semantic_match' : 'partner_box_fallback',
        ));
    }

    public function handle_adverlink_exchange_submit_publication() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project ID.');
            return;
        }

        $requester = $this->aysa_get_link_exchange_profile($project_id);
        $payload = array(
            'project_id' => $project_id,
            'current_site_domain' => $requester['site_domain'],
            'request_id' => intval(wp_unslash($_POST['request_id'] ?? 0)),
            'exchange_ref' => sanitize_text_field((string) wp_unslash($_POST['exchange_ref'] ?? '')),
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
            'published_url' => esc_url_raw(trim((string) wp_unslash($_POST['published_url'] ?? ''))),
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
            'target_url' => esc_url_raw(trim((string) wp_unslash($_POST['target_url'] ?? ''))),
            'anchor_text' => sanitize_text_field((string) wp_unslash($_POST['anchor_text'] ?? '')),
            'notes' => sanitize_textarea_field((string) wp_unslash($_POST['notes'] ?? '')),
        );

        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/publication', $payload, 30);
        if (is_wp_error($broker_result)) {
            wp_send_json_error(array(
                'code' => 'broker_error',
                'message' => $this->aysa_humanize_broker_error($broker_result, 'Could not submit publication proof.'),
            ));
            return;
        }

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_adverlink_exchange_submit_publication',
            'offpage_exchange_submit_publication_' . md5((string) ($payload['exchange_ref'] ?? '') . '|' . (string) ($payload['published_url'] ?? '')),
            array(
                'module' => 'offpage',
                'exchange_ref' => (string) ($payload['exchange_ref'] ?? ''),
            ),
            8,
            'wp_ajax/aysa_adverlink_exchange_submit_publication'
        );

        wp_send_json_success(array(
            'request' => isset($broker_result['request']) && is_array($broker_result['request']) ? $broker_result['request'] : array(),
        ));
    }

    public function handle_adverlink_exchange_refresh_verification() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project ID.');
            return;
        }

        $requester = $this->aysa_get_link_exchange_profile($project_id);
        $payload = array(
            'project_id' => $project_id,
            'current_site_domain' => $requester['site_domain'],
            'request_id' => intval(wp_unslash($_POST['request_id'] ?? 0)),
            'exchange_ref' => sanitize_text_field((string) wp_unslash($_POST['exchange_ref'] ?? '')),
        );

        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/verify', $payload, 45);
        if (is_wp_error($broker_result)) {
            wp_send_json_error(array(
                'code' => 'broker_error',
                'message' => $this->aysa_humanize_broker_error($broker_result, 'Could not refresh exchange verification.'),
            ));
            return;
        }

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_adverlink_exchange_refresh_verification',
            'offpage_exchange_refresh_verification_' . md5((string) ($payload['exchange_ref'] ?? '')),
            array(
                'module' => 'offpage',
                'exchange_ref' => (string) ($payload['exchange_ref'] ?? ''),
            ),
            6,
            'wp_ajax/aysa_adverlink_exchange_refresh_verification'
        );

        wp_send_json_success(array(
            'request' => isset($broker_result['request']) && is_array($broker_result['request']) ? $broker_result['request'] : array(),
        ));
    }

    public function handle_adverlink_exchange_change_state() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project ID.');
            return;
        }

        $requester = $this->aysa_get_link_exchange_profile($project_id);
        $payload = array(
            'project_id' => $project_id,
            'current_site_domain' => $requester['site_domain'],
            'request_id' => intval(wp_unslash($_POST['request_id'] ?? 0)),
            'exchange_ref' => sanitize_text_field((string) wp_unslash($_POST['exchange_ref'] ?? '')),
            'action' => $state_action,
            'reason' => sanitize_textarea_field((string) wp_unslash($_POST['reason'] ?? '')),
        );

        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/state', $payload, 30);
        if (is_wp_error($broker_result)) {
            wp_send_json_error(array(
                'code' => 'broker_error',
                'message' => $this->aysa_humanize_broker_error($broker_result, 'Could not update exchange request state.'),
            ));
            return;
        }

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_adverlink_exchange_change_state',
            'offpage_exchange_change_state_' . md5((string) ($payload['exchange_ref'] ?? '') . '|' . (string) ($payload['action'] ?? '')),
            array(
                'module' => 'offpage',
                'action' => (string) ($payload['action'] ?? ''),
                'exchange_ref' => (string) ($payload['exchange_ref'] ?? ''),
            ),
            6,
            'wp_ajax/aysa_adverlink_exchange_change_state'
        );

        wp_send_json_success(array(
            'request' => isset($broker_result['request']) && is_array($broker_result['request']) ? $broker_result['request'] : array(),
        ));
    }

    public function schedule_adverlink_link_issues_cron() {
        if (!wp_next_scheduled('aysa_adverlink_sync_link_issues_event')) {
            wp_schedule_event(time() + 120, 'hourly', 'aysa_adverlink_sync_link_issues_event');
        }
    }

    public function handle_adverlink_cron_link_issues_sync() {
        $project_id = intval(get_option('aysa_project_id', 0));
        if ($project_id <= 0) {
            return;
        }
        $result = $this->aysa_fetch_adverlink_link_issues($project_id);
        if (is_wp_error($result)) {
            aysa_debug_log('[AYSA Adverlink] Link issues cron sync failed: ' . $result->get_error_message());
            return;
        }
        $items = (is_array($result) && isset($result['items']) && is_array($result['items'])) ? $result['items'] : array();
        $this->aysa_save_adverlink_link_issues($project_id, $items, 'cron');
    }

    public function schedule_adverlink_subscription_renewal_cron() {
        if (!wp_next_scheduled('aysa_adverlink_process_subscription_renewals_event')) {
            wp_schedule_event(time() + 300, 'hourly', 'aysa_adverlink_process_subscription_renewals_event');
        }
    }

    public function schedule_adverlink_exchange_verification_cron() {
        if (!wp_next_scheduled('aysa_adverlink_exchange_verify_event')) {
            wp_schedule_event(time() + 180, 'aysa_every_fifteen_minutes', 'aysa_adverlink_exchange_verify_event');
        }
    }

    private function aysa_exchange_verification_is_due(array $request, $now = null) {
        $status = sanitize_key((string) ($request['status'] ?? ''));
        $interval = match ($status) {
            'verification_pending' => 15 * MINUTE_IN_SECONDS,
            'disputed' => HOUR_IN_SECONDS,
            'completed' => DAY_IN_SECONDS,
            default => 0,
        };
        if ($interval <= 0) {
            return false;
        }

        $last_verified_at = trim((string) ($request['last_verified_at'] ?? ''));
        $last_verified_ts = $last_verified_at !== '' ? strtotime($last_verified_at) : 0;
        if ($last_verified_ts <= 0) {
            return true;
        }

        $now = is_numeric($now) ? intval($now) : time();
        return ($now - $last_verified_ts) >= $interval;
    }

    public function handle_adverlink_cron_subscription_renewals() {
        $project_id = intval(get_option('aysa_project_id', 0));
        if ($project_id <= 0) {
            return;
        }

        $creds = $this->aysa_get_adverlink_credentials();
        $api_key = trim((string) ($creds['api_key'] ?? ''));
        $advertiser_org_id = trim((string) ($creds['org_id'] ?? ''));
        if ($api_key === '') {
            return;
        }
        $credentials_check = $this->aysa_validate_adverlink_marketplace_credentials($creds);
        if (is_wp_error($credentials_check)) {
            return;
        }

        $orders = $this->aysa_get_adverlink_orders($project_id);
        if (!is_array($orders) || empty($orders)) {
            return;
        }

        $actual_credits = $this->aysa_get_project_credit_balance($project_id, true);
        $reserved_credits = $this->aysa_reconcile_reserved_credits($project_id, $orders);
        $available_credits = max(0, $actual_credits - $reserved_credits);
        $wallet_manager = $this->aysa_get_wallet_manager_instance();
        $changed = false;
        $now = time();

        foreach ($orders as &$order) {
            if (!$this->aysa_is_subscription_order($order)) {
                continue;
            }

            $status = (string) ($order['status'] ?? '');
            if ($this->aysa_is_subscription_renewal_blocked_status($status)) {
                continue;
            }
            if (!$this->aysa_is_subscription_renewal_eligible_status($status)) {
                continue;
            }

            $subscription = isset($order['subscription']) && is_array($order['subscription']) ? $order['subscription'] : array();
            if (empty($subscription)) {
                continue;
            }
            if (strtolower(trim((string) ($subscription['status'] ?? ''))) !== 'active') {
                continue;
            }

            $next_renewal_ts = $this->aysa_subscription_next_renewal_timestamp($order);
            if ($next_renewal_ts <= 0 || $next_renewal_ts > $now) {
                continue;
            }

            $last_attempt_ts = intval($order['last_subscription_renewal_attempt_ts'] ?? 0);
            if ($last_attempt_ts > 0 && ($now - $last_attempt_ts) < 1800) {
                continue;
            }

            $order_ref = sanitize_text_field((string) ($order['order_ref'] ?? ''));
            if ($order_ref === '') {
                continue;
            }

            $provider_cost_cents = max(0, intval($order['provider_cost_cents'] ?? ($order['charged_credits_cents'] ?? 0)));
            if ($provider_cost_cents <= 0) {
                continue;
            }

            $service_type = sanitize_text_field((string) ($order['service_type'] ?? ''));
            $included_link_applied = false;
            if ($wallet_manager && $this->aysa_is_included_link_eligible_service($service_type) && method_exists($wallet_manager, 'has_included_link_available')) {
                $included_link_applied = (bool) $wallet_manager->has_included_link_available($project_id);
            }

            $renewal_cost_cents = $included_link_applied ? 0 : $provider_cost_cents;
            if ($available_credits < $renewal_cost_cents) {
                $order['last_subscription_renewal_attempt_ts'] = $now;
                $order['last_subscription_renewal_error'] = 'insufficient_credits';
                $order['updated_at'] = current_time('mysql');
                $changed = true;
                continue;
            }

            $idempotency_key = 'aysa-renew-' . $project_id . '-' . preg_replace('/[^A-Za-z0-9]/', '', $order_ref) . '-' . gmdate('YmdHi');
            $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/public/orders/renew', array(
                'project_id' => $project_id,
                'api_key' => $api_key,
                'advertiser_org_id' => $advertiser_org_id,
                'order_ref' => $order_ref,
                'idempotency_key' => $idempotency_key,
            ), 45);
            $order['last_subscription_renewal_attempt_ts'] = $now;

            if (is_wp_error($broker_result)) {
                $order['last_subscription_renewal_error'] = $broker_result->get_error_message();
                $order['updated_at'] = current_time('mysql');
                $changed = true;
                continue;
            }

            $decoded = is_array($broker_result['provider_response'] ?? null)
                ? $broker_result['provider_response']
                : (is_array($broker_result['data'] ?? null) ? $broker_result['data'] : $broker_result);
            $subscription_after = $this->aysa_extract_external_subscription($decoded);
            if (empty($subscription_after)) {
                $order['last_subscription_renewal_error'] = 'missing_subscription_in_response';
                $order['updated_at'] = current_time('mysql');
                $changed = true;
                continue;
            }

            if ($included_link_applied && $wallet_manager && method_exists($wallet_manager, 'consume_included_link')) {
                $consumption = $wallet_manager->consume_included_link(
                    $project_id,
                    'adverlink_subscription_renewal:' . $order_ref,
                    array(
                        'module' => 'offpage',
                        'service_type' => $service_type,
                        'order_ref' => $order_ref,
                        'subscription_id' => sanitize_text_field((string) ($subscription_after['id'] ?? '')),
                        'provider_cost_cents' => $provider_cost_cents,
                    )
                );
                if (is_wp_error($consumption)) {
                    $included_link_applied = false;
                    $renewal_cost_cents = $provider_cost_cents;
                } else {
                    $order['included_link_snapshot'] = $consumption;
                }
            }

            if ($renewal_cost_cents > 0) {
                $this->aysa_wallet_track_server_usage(
                    $project_id,
                    'wp_cron/aysa_adverlink_process_subscription_renewals',
                    $renewal_cost_cents,
                    'offpage_subscription_renew_' . $order_ref . '_' . gmdate('YmdHis'),
                    array(
                        'module' => 'offpage',
                        'action_key' => 'aysa_adverlink_subscription_renew',
                        'service_type' => $service_type,
                        'order_ref' => $order_ref,
                        'subscription_id' => sanitize_text_field((string) ($subscription_after['id'] ?? '')),
                        'provider_cost_cents' => $provider_cost_cents,
                        'included_link_applied' => false,
                    )
                );
                $available_credits -= $renewal_cost_cents;
            }

            $order['subscription'] = $subscription_after;
            $order['updated_at'] = current_time('mysql');
            $order['last_subscription_renewed_at'] = current_time('mysql');
            $order['last_subscription_renewal_error'] = '';
            $order['renewal_count'] = intval($order['renewal_count'] ?? 0) + 1;
            $order['raw_subscription_renew'] = $decoded;
            $order['provider_cost_cents'] = $provider_cost_cents;
            $order['charged_credits_cents'] = $renewal_cost_cents;
            $order['included_link_applied'] = $included_link_applied;
            $changed = true;
        }
        unset($order);

        if ($changed) {
            $this->aysa_save_adverlink_orders($project_id, $orders);
        }
    }

    public function handle_adverlink_cron_exchange_verification() {
        $project_id = intval(get_option('aysa_project_id', 0));
        if ($project_id <= 0) {
            return;
        }

        $requester = $this->aysa_get_link_exchange_profile($project_id);
        if (empty($requester['site_domain'])) {
            return;
        }

        $list = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/list', array(
            'project_id' => $project_id,
            'current_site_domain' => $requester['site_domain'],
            'direction' => 'all',
            'limit' => 200,
        ), 45);
        if (is_wp_error($list)) {
            aysa_debug_log('[AYSA Exchange] Verification cron list failed: ' . $list->get_error_message());
            return;
        }

        $items = isset($list['items']) && is_array($list['items']) ? $list['items'] : array();
        foreach ($items as $request) {
            if (!is_array($request)) {
                continue;
            }

            $auto_activation = $this->aysa_exchange_auto_activate_request($project_id, $request);
            if (is_array($auto_activation) && (string) ($auto_activation['status'] ?? '') === 'activated') {
                $this->aysa_set_exchange_auto_activation_status($project_id, (string) ($request['exchange_ref'] ?? ''), 'activated', '');
                aysa_debug_log('[AYSA Exchange] Auto-activated ' . (string) ($request['exchange_ref'] ?? '-'));
                continue;
            }
            if (is_array($auto_activation) && (string) ($auto_activation['status'] ?? '') === 'skipped' && (string) ($auto_activation['reason'] ?? '') === 'auto_activation_disabled') {
                $this->aysa_set_exchange_auto_activation_status($project_id, (string) ($request['exchange_ref'] ?? ''), 'blocked', 'autoactivation_off');
            }
            if (is_array($auto_activation) && (string) ($auto_activation['status'] ?? '') === 'failed') {
                $failed_reason = (string) ($auto_activation['reason'] ?? '');
                $this->aysa_set_exchange_auto_activation_status(
                    $project_id,
                    (string) ($request['exchange_ref'] ?? ''),
                    'failed',
                    $failed_reason === 'exchange_source_required' ? 'no_safe_page' : $failed_reason
                );
                aysa_debug_log('[AYSA Exchange] Auto-activation failed for ' . (string) ($request['exchange_ref'] ?? '-') . ': ' . (string) ($auto_activation['message'] ?? $auto_activation['reason'] ?? 'unknown'));
            }

            $viewer_role = (string) ($request['viewer_role'] ?? '');
            $viewer_owes_activation = ($viewer_role === 'requester' && (string) ($request['current_obligation'] ?? '') === 'requester_activation_required')
                || ($viewer_role === 'counterparty' && (string) ($request['current_obligation'] ?? '') === 'counterparty_activation_required');
            $local_activated = ($viewer_role === 'requester')
                ? ((string) ($request['requester_activation_state'] ?? '') === 'active')
                : ((string) ($request['counterparty_activation_state'] ?? '') === 'active');
            $remote_activated = ($viewer_role === 'requester')
                ? ((string) ($request['counterparty_activation_state'] ?? '') === 'active')
                : ((string) ($request['requester_activation_state'] ?? '') === 'active');

            $remote_activation_event = $this->aysa_exchange_find_latest_remote_activation_event($request);
            if ($viewer_owes_activation && !$local_activated && is_array($remote_activation_event)) {
                $event_marker = md5((string) ($request['exchange_ref'] ?? '') . '|' . (string) ($remote_activation_event['created_at'] ?? ''));
                $initial_key = 'activation_email:' . $event_marker;
                if (!$this->aysa_exchange_notification_sent($project_id, $initial_key)) {
                    if ($this->aysa_exchange_send_activation_email($project_id, $request, 'activation')) {
                        $this->aysa_mark_exchange_notification_sent($project_id, $initial_key);
                    }
                }

                $event_ts = strtotime((string) ($remote_activation_event['created_at'] ?? ''));
                $reminder_hours = $this->aysa_exchange_activation_reminder_hours();
                if ($event_ts > 0 && (time() - $event_ts) >= ($reminder_hours * HOUR_IN_SECONDS)) {
                    $reminder_key = 'activation_email_reminder:' . $event_marker;
                    if (!$this->aysa_exchange_notification_sent($project_id, $reminder_key)) {
                        if ($this->aysa_exchange_send_activation_email($project_id, $request, 'reminder_24h')) {
                            $this->aysa_mark_exchange_notification_sent($project_id, $reminder_key);
                        }
                    }
                }
            }

            if ($local_activated && !$remote_activated) {
                $local_published_at = $viewer_role === 'requester'
                    ? (string) ($request['requester_published_at'] ?? '')
                    : (string) ($request['counterparty_published_at'] ?? '');
                $local_source_post_id = $viewer_role === 'requester'
                    ? intval($request['requester_source_post_id'] ?? 0)
                    : intval($request['counterparty_source_post_id'] ?? 0);
                $published_ts = $local_published_at !== '' ? strtotime($local_published_at) : 0;
                if ($published_ts > 0 && (time() - $published_ts) >= ($this->aysa_exchange_activation_grace_hours() * HOUR_IN_SECONDS)) {
                    if ($local_source_post_id > 0) {
                        $cleanup = $this->aysa_exchange_remove_link_from_post($local_source_post_id, (string) ($request['exchange_ref'] ?? ''));
                        if (is_wp_error($cleanup)) {
                            aysa_debug_log('[AYSA Exchange] Auto-expire cleanup failed for ' . (string) ($request['exchange_ref'] ?? '-') . ': ' . $cleanup->get_error_message());
                        }
                    }

                    $expire = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/state', array(
                        'project_id' => $project_id,
                        'current_site_domain' => $requester['site_domain'],
                        'request_id' => intval($request['id'] ?? 0),
                        'exchange_ref' => sanitize_text_field((string) ($request['exchange_ref'] ?? '')),
                        'action' => 'expire',
                        'reason' => 'Auto-expired after 48 hours without reciprocal activation.',
                    ), 30);
                    if (is_wp_error($expire)) {
                        aysa_debug_log('[AYSA Exchange] Auto-expire failed for ' . (string) ($request['exchange_ref'] ?? '-') . ': ' . $expire->get_error_message());
                    }
                    continue;
                }
            }

            $status = (string) ($request['status'] ?? '');
            $has_both_urls = !empty($request['requester_published_url']) && !empty($request['counterparty_published_url']);
            if (!$has_both_urls) {
                continue;
            }
            if (!in_array($status, array('verification_pending', 'completed', 'disputed'), true)) {
                continue;
            }
            if (!$this->aysa_exchange_verification_is_due($request)) {
                continue;
            }

            $verify = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/verify', array(
                'project_id' => $project_id,
                'current_site_domain' => $requester['site_domain'],
                'request_id' => intval($request['id'] ?? 0),
                'exchange_ref' => sanitize_text_field((string) ($request['exchange_ref'] ?? '')),
            ), 45);
            if (is_wp_error($verify)) {
                aysa_debug_log('[AYSA Exchange] Verification cron refresh failed for ' . (string) ($request['exchange_ref'] ?? '-') . ': ' . $verify->get_error_message());
            }
        }
    }

    public function handle_adverlink_get_link_issues() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project ID.');
            return;
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $force_refresh = isset($_POST['force_refresh']) && (string) wp_unslash($_POST['force_refresh']) === '1';
        if ($force_refresh) {
            $result = $this->aysa_fetch_adverlink_link_issues($project_id);
            if (is_wp_error($result)) {
                wp_send_json_error($result->get_error_message());
                return;
            }
            $items = (is_array($result) && isset($result['items']) && is_array($result['items'])) ? $result['items'] : array();
            $this->aysa_save_adverlink_link_issues($project_id, $items, 'manual');
        }

        $items = get_option($this->aysa_get_adverlink_link_issues_key($project_id), array());
        if (!is_array($items)) {
            $items = array();
        }
        $last_sync = get_option($this->aysa_get_adverlink_link_issues_sync_key($project_id), array());
        if (!is_array($last_sync)) {
            $last_sync = array();
        }

        wp_send_json_success(array(
            'items' => array_values($items),
            'last_sync' => $last_sync,
        ));
    }

    public function handle_adverlink_create_order() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        $creds = $this->aysa_get_adverlink_credentials();
        $api_key = $creds['api_key'];

        $portal_id = sanitize_text_field((string) wp_unslash($_POST['portal_id'] ?? ''));
        $service_type = sanitize_text_field((string) wp_unslash($_POST['service_type'] ?? 'link_insertion'));
        $billing_type = sanitize_text_field((string) wp_unslash($_POST['billing_type'] ?? 'one_time'));
        $keyword = sanitize_text_field((string) wp_unslash($_POST['keyword'] ?? ''));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $target_url = esc_url_raw(trim((string) wp_unslash($_POST['target_url'] ?? '')));
        $title = sanitize_text_field((string) wp_unslash($_POST['title'] ?? ''));
        $rel_type = sanitize_text_field((string) wp_unslash($_POST['rel_type'] ?? 'follow'));
        $client_ref = sanitize_text_field((string) wp_unslash($_POST['client_ref'] ?? ''));
        $client_name = sanitize_text_field((string) wp_unslash($_POST['client_name'] ?? ''));
        $project_ref = sanitize_text_field((string) wp_unslash($_POST['project_ref'] ?? ''));
        $article_topic = sanitize_text_field((string) wp_unslash($_POST['article_topic'] ?? ''));
        $article_brief = sanitize_textarea_field((string) wp_unslash($_POST['article_brief'] ?? ''));
        $article_word_count = intval(wp_unslash($_POST['article_word_count'] ?? 0));
        $article_language = sanitize_text_field((string) wp_unslash($_POST['article_language'] ?? ''));
        $article_tone = sanitize_text_field((string) wp_unslash($_POST['article_tone'] ?? ''));
        $article_content_html = wp_kses_post((string) wp_unslash($_POST['article_content_html'] ?? ''));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $ad_display_url = esc_url_raw(trim((string) wp_unslash($_POST['ad_display_url'] ?? '')));
        $ad_description = sanitize_textarea_field((string) wp_unslash($_POST['ad_description'] ?? ''));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $target_article_url = esc_url_raw(trim((string) wp_unslash($_POST['target_article_url'] ?? '')));
        $duration_months = max(1, min(12, intval(wp_unslash($_POST['duration_months'] ?? 1))));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $article_keywords_raw = (string) wp_unslash($_POST['article_keywords'] ?? '');
        $article_keywords = array();
        if ($article_keywords_raw !== '') {
            $decoded_keywords = json_decode(wp_unslash($article_keywords_raw), true);
            if (is_array($decoded_keywords)) {
                foreach ($decoded_keywords as $kw) {
                    $kw = sanitize_text_field((string) $kw);
                    if ($kw !== '') {
                        $article_keywords[] = $kw;
                    }
                }
            } else {
                $split_keywords = array_map('trim', explode(',', $article_keywords_raw));
                foreach ($split_keywords as $kw) {
                    $kw = sanitize_text_field((string) $kw);
                    if ($kw !== '') {
                        $article_keywords[] = $kw;
                    }
                }
            }
        }
        $estimated_credits = max(0, intval(wp_unslash($_POST['estimated_credits'] ?? 0)));
        $portal_domain = sanitize_text_field((string) wp_unslash($_POST['portal_domain'] ?? ''));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $order_meta_json = (string) wp_unslash($_POST['order_meta_json'] ?? '');
        $order_meta = array();
        if ($order_meta_json !== '') {
            $decoded_meta = json_decode(wp_unslash($order_meta_json), true);
            if (is_array($decoded_meta)) {
                $order_meta = $decoded_meta;
            }
        }

        if ($project_id <= 0) {
            wp_send_json_error('Invalid project ID.');
            return;
        }

        $credentials_check = $this->aysa_validate_adverlink_marketplace_credentials($creds);
        if (is_wp_error($credentials_check)) {
            wp_send_json_error($this->aysa_format_adverlink_marketplace_error($credentials_check));
            return;
        }

        if (empty($portal_id) || empty($keyword) || empty($target_url)) {
            wp_send_json_error('Missing required fields: portal_id, keyword, target_url.');
            return;
        }
        if ($client_ref === '') {
            $client_ref = 'AYSA-' . $project_id . '-' . gmdate('YmdHis') . '-' . wp_generate_password(6, false, false);
        }
        if ($client_name === '') {
            $client_name = sanitize_text_field(get_bloginfo('name'));
        }
        if ($project_ref === '') {
            $project_ref = 'wp-project-' . $project_id;
        }

        if ($estimated_credits <= 0) {
            wp_send_json_error('Unable to determine order cost. Please refresh portals and retry.');
            return;
        }

        $payload = array(
            'portal_id' => $portal_id,
            'portal_domain' => $portal_domain,
            'service_type' => $service_type,
            'billing_type' => $billing_type,
            'keyword' => $keyword,
            'target_url' => $target_url,
            'title' => $title,
            'rel_type' => $rel_type,
            'client_ref' => $client_ref,
            'client_name' => $client_name,
            'project_ref' => $project_ref,
            'estimated_credits' => $estimated_credits,
            'order_meta' => $order_meta,
        );
        if ($service_type === 'seo_article' || $service_type === 'professional_article') {
            if ($article_topic !== '') {
                $payload['article_topic'] = $article_topic;
            }
            if ($article_brief !== '') {
                $payload['article_brief'] = $article_brief;
            }
            if ($article_word_count > 0) {
                $payload['article_word_count'] = $article_word_count;
            }
            if ($article_language !== '') {
                $payload['article_language'] = $article_language;
            }
            if ($article_tone !== '') {
                $payload['article_tone'] = $article_tone;
            }
            if (!empty($article_keywords)) {
                $payload['article_keywords'] = array_values(array_unique($article_keywords));
            }
            if ($article_content_html !== '') {
                $payload['article_content_html'] = $article_content_html;
            }
            // Adverlink SEO articles should not force sponsored rel on article links.
            $payload['rel_type'] = $rel_type === 'nofollow' ? 'nofollow' : 'follow';
        } elseif ($service_type === 'seo_ads_text') {
            if ($title === '' || $ad_display_url === '' || $ad_description === '') {
                wp_send_json_error('Missing required fields for seo_ads_text: title, ad_display_url, ad_description.');
                return;
            }
            if (function_exists('mb_strlen') && mb_strlen($ad_description, 'UTF-8') > 90) {
                wp_send_json_error('ad_description max length is 90 characters.');
                return;
            } elseif (!function_exists('mb_strlen') && strlen($ad_description) > 90) {
                wp_send_json_error('ad_description max length is 90 characters.');
                return;
            }
            $payload['ad_display_url'] = $ad_display_url;
            $payload['ad_description'] = $ad_description;
            // SEO text ads should not force sponsored rel on inserted href.
            $payload['rel_type'] = 'follow';
            if ($target_article_url !== '') {
                $payload['target_article_url'] = $target_article_url;
            }
            $payload['duration_months'] = $duration_months;
        }
        $order_result = $this->aysa_adjustments_create_adverlink_order_internal($project_id, $payload);
        if (is_wp_error($order_result)) {
            if ($order_result->get_error_code() === 'insufficient_credits') {
                $error_data = $order_result->get_error_data();
                $payment_response = $this->aysa_build_insufficient_credits_payment_response(
                    $project_id,
                    intval(is_array($error_data) ? ($error_data['required_credits'] ?? 0) : 0),
                    intval(is_array($error_data) ? ($error_data['available_credits'] ?? 0) : 0),
                    'en',
                    match ($service_type) {
                        'professional_article' => 'professional_article_order',
                        'seo_article' => 'seo_article_order',
                        'link_insertion' => 'link_insertion_order',
                        'seo_ads_text' => 'seo_text_ads_order',
                        default => 'order',
                    },
                    'offpage_link_building',
                    array(
                        'portal_id' => $portal_id,
                        'portal_domain' => $portal_domain,
                        'target_url' => $target_url,
                        'action_ref' => sanitize_key((string) $service_type) . '_order|' . $portal_id . '|' . $target_url,
                    )
                );
                wp_send_json_error(array(
                    'message' => 'Insufficient credits.',
                    'assistant_message' => (string) ($payment_response['assistant_message'] ?? ''),
                    'payment_link' => (string) ($payment_response['payment_link'] ?? ''),
                    'required_credits' => intval($payment_response['required_credits'] ?? 0),
                    'available_credits' => intval($payment_response['available_credits'] ?? 0),
                    'missing_credits' => intval($payment_response['missing_credits'] ?? 0),
                ));
                return;
            }
            wp_send_json_error('API request failed: ' . $order_result->get_error_message());
            return;
        }

        $publisher_draft = null;
        $publisher_draft_warning = '';
        if ($service_type === 'seo_article' && is_array($order_result)) {
            $draft_result = $this->aysa_create_publisher_article_draft_for_order($project_id, $order_result);
            if (is_wp_error($draft_result)) {
                $publisher_draft_warning = $draft_result->get_error_message();
            } elseif (isset($draft_result['draft']) && is_array($draft_result['draft'])) {
                $publisher_draft = $draft_result['draft'];
            }
        }

        wp_send_json_success(array(
            'order_ref' => sanitize_text_field((string) ($order_result['order_ref'] ?? '')),
            'status' => sanitize_text_field((string) ($order_result['status'] ?? 'created')),
            'charged_credits_cents' => intval($order_result['charged_credits_cents'] ?? 0),
            'provider_cost_cents' => intval($order_result['provider_cost_cents'] ?? 0),
            'included_link_applied' => !empty($order_result['included_link_applied']),
            'included_link_snapshot' => is_array($order_result['included_link_snapshot'] ?? null) ? $order_result['included_link_snapshot'] : null,
            'actual_credits' => intval($order_result['actual_credits'] ?? 0),
            'reserved_credits' => intval($order_result['reserved_credits'] ?? 0),
            'available_credits' => intval($order_result['available_credits'] ?? 0),
            'raw' => is_array($order_result['raw'] ?? null) ? $order_result['raw'] : array(),
            'publisher_article_draft' => $publisher_draft,
            'publisher_article_draft_warning' => $publisher_draft_warning,
        ));
    }

    public function handle_adverlink_get_orders() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project ID.');
            return;
        }

        $orders = $this->aysa_get_adverlink_orders($project_id);
        $changed = false;
        foreach ($orders as &$order) {
            $current_ref = sanitize_text_field((string) ($order['order_ref'] ?? ''));
            $portal_id = sanitize_text_field((string) ($order['portal_id'] ?? ''));
            $raw_source = null;
            if (isset($order['raw_track']) && is_array($order['raw_track'])) {
                $raw_source = $order['raw_track'];
            } elseif (isset($order['raw_response']) && is_array($order['raw_response'])) {
                $raw_source = $order['raw_response'];
            }

            if (is_array($raw_source)) {
                $ext_ref = $this->aysa_extract_external_order_ref($raw_source);
                $ref_is_placeholder = (strpos($current_ref, 'local-') === 0)
                    || ($current_ref !== '' && $portal_id !== '' && $current_ref === $portal_id);
                if ($ext_ref !== '' && $ref_is_placeholder) {
                    $order['order_ref'] = $ext_ref;
                    $current_ref = $ext_ref;
                    $changed = true;
                }
                $ext_status = $this->aysa_extract_external_order_status($raw_source);
                if ($ext_status !== '' && empty($order['status'])) {
                    $order['status'] = $ext_status;
                    $changed = true;
                }
                $ext_verification = $this->aysa_extract_external_verification($raw_source);
                if (!empty($ext_verification) && empty($order['verification'])) {
                    $order['verification'] = $ext_verification;
                    $changed = true;
                }
                $ext_subscription = $this->aysa_extract_external_subscription($raw_source);
                if (!empty($ext_subscription) && empty($order['subscription'])) {
                    $order['subscription'] = $ext_subscription;
                    $changed = true;
                }
            }

            $order['is_local_ref'] = (strpos($current_ref, 'local-') === 0);
            $order['is_trackable_ref'] = ($current_ref !== '' && strpos($current_ref, 'local-') !== 0 && !($portal_id !== '' && $current_ref === $portal_id));
        }
        unset($order);
        if ($changed) {
            $this->aysa_save_adverlink_orders($project_id, $orders);
        }

        // Recompute reserved credits from current order states (including completed).
        $this->aysa_reconcile_reserved_credits($project_id, $orders);
        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_adverlink_get_orders',
            'offpage_get_orders_' . $project_id,
            array(
                'module' => 'offpage',
                'items_count' => count($orders),
            ),
            0,
            'wp_ajax/aysa_adverlink_get_orders'
        );
        wp_send_json_success(array('items' => $orders));
    }

    public function handle_adverlink_track_order() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        $creds = $this->aysa_get_adverlink_credentials();
        $api_key = $creds['api_key'];
        $advertiser_org_id = $creds['org_id'];
        $order_ref = sanitize_text_field((string) wp_unslash($_POST['order_ref'] ?? ''));

        if ($project_id <= 0 || empty($order_ref)) {
            wp_send_json_error('Missing required fields.');
            return;
        }

        $credentials_check = $this->aysa_validate_adverlink_marketplace_credentials($creds);
        if (is_wp_error($credentials_check)) {
            wp_send_json_error($this->aysa_format_adverlink_marketplace_error($credentials_check));
            return;
        }
        $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/public/orders/track', array(
            'project_id' => $project_id,
            'api_key' => $api_key,
            'advertiser_org_id' => $advertiser_org_id,
            'order_ref' => $order_ref,
        ), 45);
        if (is_wp_error($broker_result)) {
            wp_send_json_error('API request failed: ' . $broker_result->get_error_message());
            return;
        }
        $decoded = is_array($broker_result['provider_response'] ?? null)
            ? $broker_result['provider_response']
            : (is_array($broker_result['data'] ?? null) ? $broker_result['data'] : $broker_result);

        $new_status = 'unknown';
        if (is_array($decoded)) {
            $new_status = (string) $this->aysa_extract_external_order_status($decoded);
        }
        if ($new_status === '') {
            $new_status = 'unknown';
        }
        $verification = $this->aysa_extract_external_verification($decoded);
        if (
            is_array($verification) &&
            isset($verification['is_valid']) &&
            $verification['is_valid'] === true &&
            intval($verification['http_status_code'] ?? 0) === 200 &&
            in_array(strtoupper($new_status), array('AWAITING_PUBLISHER', 'PENDING', 'IN_PROGRESS', 'UNKNOWN'), true)
        ) {
            $new_status = 'COMPLETED';
        }

        $orders = $this->aysa_get_adverlink_orders($project_id);
        foreach ($orders as &$order) {
            if ((string)($order['order_ref'] ?? '') === $order_ref) {
                $order['status'] = $new_status;
                $order['updated_at'] = current_time('mysql');
                $order['raw_track'] = $decoded;
                if (!empty($verification)) {
                    $order['verification'] = $verification;
                }
                $subscription = $this->aysa_extract_external_subscription($decoded);
                if (!empty($subscription)) {
                    $order['subscription'] = $subscription;
                }
                break;
            }
        }
        unset($order);

        // Always reconcile after track updates so completed orders release reserved credits.
        $reserved_credits = $this->aysa_reconcile_reserved_credits($project_id, $orders);
        $this->aysa_save_adverlink_orders($project_id, $orders);
        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_adverlink_track_order',
            'offpage_track_order_' . $order_ref,
            array(
                'module' => 'offpage',
                'status' => $new_status,
            ),
            0,
            'wp_ajax/aysa_adverlink_track_order'
        );

        $actual_credits = $this->aysa_get_project_credit_balance($project_id);
        wp_send_json_success(array(
            'order_ref' => $order_ref,
            'status' => $new_status,
            'actual_credits' => $actual_credits,
            'reserved_credits' => $reserved_credits,
            'available_credits' => max(0, $actual_credits - $reserved_credits),
            'raw' => $decoded
        ));
    }

    /**
     * Handle AJAX request to recheck a specific link
     * Uses data from the initial API call for efficiency
     */
    public function handle_recheck_link() {
        // Verify nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }

        // Check permissions
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        // Get parameters
        $wtl_id = intval(wp_unslash($_POST['wtl_id'] ?? 0));
        
        if (!$wtl_id) {
            wp_send_json_error('Invalid link ID');
            return;
        }

        aysa_debug_log("[AYSA RECHECK] Starting optimized recheck for link ID: {$wtl_id}");
        $project_id = $this->aysa_resolve_project_id_with_local_fallback(intval(wp_unslash($_POST['project_id'] ?? 0)));
        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_recheck_link',
            'offpage_recheck_link_' . $wtl_id,
            array(
                'module' => 'offpage_legacy',
                'wtl_id' => $wtl_id,
            ),
            2,
            'wp_ajax/aysa_recheck_link'
        );

        // Simply trigger a refresh of the current data by telling frontend to reload that specific line
        // The frontend will use the same API call that was made initially
        wp_send_json_success(array(
            'message' => 'Triggering link data refresh',
            'action' => 'refresh_from_current_data',
            'wtl_id' => $wtl_id,
            'timestamp' => current_time('mysql')
        ));
    }

    /**
     * Handle AJAX request to cancel a WordToLink
     */
    public function handle_cancel_wordtolink() {
        // Verify nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }

        // Check permissions
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        // Get parameters
        $wtl_id = intval(wp_unslash($_POST['wtl_id'] ?? 0));
        
        if (!$wtl_id) {
            wp_send_json_error('Invalid WordToLink ID');
            return;
        }

        aysa_debug_log("[AYSA CANCEL] Starting cancel process for WordToLink ID: {$wtl_id}");
        $project_id = $this->aysa_resolve_project_id_with_local_fallback(intval(wp_unslash($_POST['project_id'] ?? 0)));

        // Use the centralized API configuration
        if (!class_exists('Aysa_API_Config')) {
            require_once AYSA_PLUGIN_DIR . 'includes/config/aysa-api-config.php';
        }

        try {
            // Get the full API URL using the centralized configuration
            $api_url = Aysa_API_Config::get_endpoint_url_with_params(
                'cancel_wordtolink',
                array(
                    'wtlId' => $wtl_id
                )
            );

            aysa_debug_log("[AYSA CANCEL] API URL: {$api_url}");

            if (!$api_url) {
                wp_send_json_error('Failed to generate API URL');
                return;
            }

            $secret_key = trim((string) get_option('aysa_user_secret_key', ''));
            if ($secret_key === '') {
                wp_send_json_error('AYSA authentication is required before cancelling WordToLink orders.');
                return;
            }

            aysa_debug_log("[AYSA CANCEL] Making DELETE API call to: {$api_url}");

            $response = wp_remote_request($api_url, array(
                'method' => 'DELETE',
                'timeout' => 30,
                'headers' => array(
                    'Content-Type' => 'application/json',
                    'Authorization' => 'Bearer ' . $secret_key,
                )
            ));

            aysa_debug_log("[AYSA CANCEL] API call completed");

            if (is_wp_error($response)) {
                aysa_debug_log("[AYSA CANCEL] API request failed: " . $response->get_error_message());
                wp_send_json_error('API request failed: ' . $response->get_error_message());
                return;
            }

            $response_code = wp_remote_retrieve_response_code($response);
            $response_body = wp_remote_retrieve_body($response);

            aysa_debug_log("[AYSA CANCEL] API response code: {$response_code}");
            aysa_debug_log("[AYSA CANCEL] API response body: " . substr($response_body, 0, 500));

            if ($response_code === 200 || $response_code === 204) {
                // Success - WordToLink cancelled
                aysa_debug_log("[AYSA CANCEL] WordToLink {$wtl_id} cancelled successfully");
                $this->aysa_wallet_track_action_usage(
                    $project_id,
                    'aysa_cancel_wordtolink',
                    'offpage_cancel_wordtolink_' . $wtl_id,
                    array(
                        'module' => 'offpage_legacy',
                        'wtl_id' => $wtl_id,
                        'response_code' => $response_code,
                        'source' => 'remote',
                    ),
                    10,
                    'wp_ajax/aysa_cancel_wordtolink'
                );
                
                wp_send_json_success(array(
                    'message' => 'WordToLink cancelled successfully',
                    'wtl_id' => $wtl_id,
                    'action' => 'cancelled',
                    'timestamp' => current_time('mysql'),
                    'api_response_code' => $response_code
                ));
            } else if ($response_code === 401) {
                aysa_debug_log("[AYSA CANCEL] API returned 401 (Unauthorized).");
                wp_send_json_error('AYSA authentication failed while cancelling WordToLink order.');
            } else if ($response_code === 404) {
                // WordToLink not found or already cancelled
                aysa_debug_log("[AYSA CANCEL] WordToLink {$wtl_id} not found or already cancelled");
                
                wp_send_json_error('WordToLink not found or already cancelled');
            } else {
                // Other error
                aysa_debug_log("[AYSA CANCEL] API returned error code: {$response_code}");
                
                $error_message = 'API returned error: ' . $response_code;
                
                // Try to parse error message from response
                if (!empty($response_body)) {
                    $error_data = json_decode($response_body, true);
                    if (json_last_error() === JSON_ERROR_NONE && isset($error_data['message'])) {
                        $error_message = $error_data['message'];
                    }
                }
                
                wp_send_json_error($error_message);
            }

        } catch (Exception $e) {
            aysa_debug_log("[AYSA CANCEL] Exception during cancel: " . $e->getMessage());
            wp_send_json_error('Error during cancellation: ' . $e->getMessage());
        }
    }
    
    /**
     * AJAX handler: Cleanup SuggestedKeywords
     */
    public function handle_cleanup_suggested_keywords() {
        if (!$this->aysa_guard_destructive_tool_request()) {
            return;
        }

        // Security checks
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Access denied');
            return;
        }
        
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'aysa-cleanup')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        
        $project_id = $this->aysa_resolve_installed_project_id(isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid Project ID');
            return;
        }
        
        try {
            if (!class_exists('Aysa_Auth_Manager')) {
                require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
            }
            
            $auth_manager = Aysa_Auth_Manager::get_instance();
            $keywords_url = 'api/SuggestedKeywords/ByProject/' . $project_id;
            $keywords_response = $auth_manager->make_authenticated_request($keywords_url, 'GET');
            
            $deleted = 0;
            $errors = [];
            
            if ($keywords_response !== false && is_array($keywords_response)) {
                // Check if response is wrapped
                if (isset($keywords_response['data']) && is_array($keywords_response['data'])) {
                    $keywords_response = $keywords_response['data'];
                }
                
                foreach ($keywords_response as $keyword) {
                    $keyword_id = isset($keyword['id']) ? $keyword['id'] : (isset($keyword['Id']) ? $keyword['Id'] : null);
                    if ($keyword_id) {
                        $delete_url = 'api/SuggestedKeywords/' . $keyword_id;
                        $delete_response = $auth_manager->make_authenticated_request($delete_url, 'DELETE');
                        
                        if ($delete_response !== false) {
                            $deleted++;
                        } else {
                            $errors[] = 'Failed to delete keyword ID: ' . $keyword_id;
                        }
                    }
                }
            }
            
            $this->invalidate_suggested_keywords_cache($project_id);

            wp_send_json_success(array(
                'deleted' => $deleted,
                'errors' => $errors
            ));
            
        } catch (Exception $e) {
            wp_send_json_error('Error: ' . $e->getMessage());
        }
    }

    /**
     * Build cache key for suggested keywords.
     */
    private function get_suggested_keywords_cache_key($project_id) {
        return 'aysa_suggested_keywords_cache_' . intval($project_id);
    }

    /**
     * Remove cached suggested keywords for a project.
     */
    private function invalidate_suggested_keywords_cache($project_id) {
        $cache_key = $this->get_suggested_keywords_cache_key($project_id);
        delete_transient($cache_key);
    }

    /**
     * AJAX handler: get suggested keywords with transient caching.
     */
    public function handle_get_suggested_keywords_cached() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_keyword_cache_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }

        if (!current_user_can('manage_options')) {
            wp_send_json_error('Access denied');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(isset($_POST['project_id']) ? intval(wp_unslash($_POST['project_id'])) : 0);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project ID');
            return;
        }

        $force_refresh = isset($_POST['force_refresh']) && in_array(strtolower((string) wp_unslash($_POST['force_refresh'])), array('1', 'true', 'yes'), true);
        $cache_ttl = intval(apply_filters('aysa_suggested_keywords_cache_ttl', 15 * MINUTE_IN_SECONDS));
        if ($cache_ttl <= 0) {
            $cache_ttl = 15 * MINUTE_IN_SECONDS;
        }

        $cache_key = $this->get_suggested_keywords_cache_key($project_id);
        $cached_payload = get_transient($cache_key);

        if (!$force_refresh && is_array($cached_payload) && isset($cached_payload['keywords']) && is_array($cached_payload['keywords'])) {
            wp_send_json_success(array(
                'keywords' => $cached_payload['keywords'],
                'cached' => true,
                'fetched_at' => isset($cached_payload['fetched_at']) ? intval($cached_payload['fetched_at']) : 0,
                'cache_ttl' => $cache_ttl
            ));
            return;
        }

        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }

        try {
            $auth_manager = Aysa_Auth_Manager::get_instance();
            $decoded = $auth_manager->make_authenticated_request('api/SuggestedKeywords/ByProject/' . rawurlencode((string) $project_id), 'GET');
        } catch (Exception $e) {
            wp_send_json_error('Failed to fetch suggested keywords: ' . $e->getMessage());
            return;
        }

        $keywords = $this->normalize_aysa_collection_response($decoded);
        if (empty($keywords) && is_array($decoded)) {
            if (isset($decoded['keywords']) && is_array($decoded['keywords'])) {
                $keywords = array_values($decoded['keywords']);
            } elseif (isset($decoded['Keywords']) && is_array($decoded['Keywords'])) {
                $keywords = array_values($decoded['Keywords']);
            }
        }

        $payload = array(
            'keywords' => $keywords,
            'fetched_at' => time()
        );
        set_transient($cache_key, $payload, $cache_ttl);

        wp_send_json_success(array(
            'keywords' => $keywords,
            'cached' => false,
            'fetched_at' => $payload['fetched_at'],
            'cache_ttl' => $cache_ttl
        ));
    }

    public function handle_keyword_discovery_get_associations() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        // Local-first mode for Keyword Discovery load:
        // - avoids remote timeouts on passive page loads
        // - avoids credit consumption for read-only local associations
        // Remote can still be requested explicitly via force_remote=1.
        $force_remote = !empty($_POST['force_remote']) && intval(wp_unslash($_POST['force_remote'])) === 1;
        if (!$force_remote) {
            $local_rows = $this->get_keyword_discovery_local_associations($project_id);
            wp_send_json_success(array(
                'project_id' => $project_id,
                'associations' => array_values($local_rows),
                'count' => count($local_rows),
                'source' => 'local_only',
            ));
            return;
        }

        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }

        try {
            $auth_manager = Aysa_Auth_Manager::get_instance();
            $raw = $auth_manager->make_authenticated_request(
                'api/Association?projectId=' . $project_id . '&pageSize=1000&pageNumber=1&sortOrder=asc',
                'GET'
            );
            $rows = $this->normalize_aysa_collection_response($raw);
            if (!is_array($rows)) {
                $rows = array();
            }

            $rows = $this->merge_keyword_discovery_associations(
                $rows,
                $this->get_keyword_discovery_local_associations($project_id)
            );

            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_keyword_discovery_get_associations',
                'keyword_discovery_get_associations',
                array(
                    'module' => 'keyword_discovery',
                    'source' => 'remote_with_local_merge',
                    'count' => count($rows),
                ),
                5,
                'api/Association'
            );

            wp_send_json_success(array(
                'project_id' => $project_id,
                'associations' => array_values($rows),
                'count' => count($rows),
            ));
        } catch (Exception $e) {
            $local_rows = $this->get_keyword_discovery_local_associations($project_id);
            wp_send_json_success(array(
                'project_id' => $project_id,
                'associations' => array_values($local_rows),
                'count' => count($local_rows),
                'source' => 'local_fallback',
                'warning' => 'Remote association API unavailable: ' . $e->getMessage(),
            ));
        }
    }

    public function handle_keyword_discovery_upsert_association() {
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : \x27\x27;
        $nonce_ok = ($nonce !== '') && (
            wp_verify_nonce($nonce, 'aysa_nonce') ||
            wp_verify_nonce($nonce, 'aysa_metabox_nonce') ||
            wp_verify_nonce($nonce, 'aysa-ajax-nonce') ||
            wp_verify_nonce($nonce, 'aysa_wizard_nonce')
        );
        if (!$nonce_ok) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $keyword = trim((string) wp_unslash($_POST['keyword'] ?? ''));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $associated_on = trim((string) wp_unslash($_POST['associated_on'] ?? ''));
        $id_intern = intval(wp_unslash($_POST['id_intern'] ?? 0));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $value = trim((string) wp_unslash($_POST['value'] ?? ''));

        if ($project_id <= 0 || $keyword === '' || $associated_on === '' || $id_intern <= 0 || $value === '') {
            wp_send_json_error('Missing required fields');
            return;
        }

        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }

        try {
            $auth_manager = Aysa_Auth_Manager::get_instance();
            $payload = array(
                'projectId' => $project_id,
                'keyword' => $keyword,
                'associatedOn' => $associated_on,
                'idIntern' => $id_intern,
                'value' => $value,
            );
            $raw = $auth_manager->make_authenticated_request('api/Association/upsert', 'PUT', $payload);
            if ($raw === false) {
                $local_assoc = $this->upsert_keyword_discovery_local_association(
                    $project_id,
                    $keyword,
                    $associated_on,
                    $id_intern,
                    $value
                );
                $this->aysa_wallet_track_action_usage(
                    $project_id,
                    'aysa_keyword_discovery_upsert_association',
                    'keyword_discovery_upsert_association',
                    array(
                        'module' => 'keyword_discovery',
                        'source' => 'local_fallback',
                        'keyword' => $keyword,
                        'associated_on' => $associated_on,
                    ),
                    5,
                    'api/Association/upsert'
                );
                wp_send_json_success(array(
                    'project_id' => $project_id,
                    'association' => $local_assoc,
                    'source' => 'local_fallback',
                ));
                return;
            }
            // Mirror successful remote upsert locally so the on-page table can always read
            // consistent DB-backed associations even when remote fetch fails later.
            $local_assoc = $this->upsert_keyword_discovery_local_association(
                $project_id,
                $keyword,
                $associated_on,
                $id_intern,
                $value
            );
            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_keyword_discovery_upsert_association',
                'keyword_discovery_upsert_association',
                array(
                    'module' => 'keyword_discovery',
                    'source' => 'remote_with_local_mirror',
                    'keyword' => $keyword,
                    'associated_on' => $associated_on,
                ),
                5,
                'api/Association/upsert'
            );
            wp_send_json_success(array(
                'project_id' => $project_id,
                'association' => is_array($raw) ? $raw : array(),
                'local_association' => $local_assoc,
                'source' => 'remote_with_local_mirror',
            ));
        } catch (Exception $e) {
            $local_assoc = $this->upsert_keyword_discovery_local_association(
                $project_id,
                $keyword,
                $associated_on,
                $id_intern,
                $value
            );
            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_keyword_discovery_upsert_association',
                'keyword_discovery_upsert_association',
                array(
                    'module' => 'keyword_discovery',
                    'source' => 'local_fallback_exception',
                    'keyword' => $keyword,
                    'associated_on' => $associated_on,
                ),
                5,
                'api/Association/upsert'
            );
            wp_send_json_success(array(
                'project_id' => $project_id,
                'association' => $local_assoc,
                'source' => 'local_fallback',
                'warning' => 'Remote association API unavailable: ' . $e->getMessage(),
            ));
        }
    }

    private function get_keyword_discovery_local_associations_option_key($project_id) {
        return 'aysa_keyword_discovery_local_associations_' . intval($project_id);
    }

    private function get_keyword_discovery_local_associations($project_id) {
        $key = $this->get_keyword_discovery_local_associations_option_key($project_id);
        $rows = get_option($key, array());
        if (!is_array($rows)) {
            return array();
        }
        $normalized = array();
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keyword = trim((string) ($row['keyword'] ?? ''));
            $associated_on = trim((string) ($row['associatedOn'] ?? $row['associated_on'] ?? ''));
            $id_intern = intval($row['idIntern'] ?? $row['id_intern'] ?? 0);
            $value = trim((string) ($row['value'] ?? ''));
            if ($keyword === '' || $associated_on === '' || $id_intern <= 0 || $value === '') {
                continue;
            }
            $normalized[] = array(
                'id' => isset($row['id']) && is_numeric($row['id'])
                    ? intval($row['id'])
                    : $this->build_keyword_discovery_stable_local_id($keyword, $associated_on, $id_intern),
                'projectId' => intval($project_id),
                'keyword' => $keyword,
                'associatedOn' => $associated_on,
                'idIntern' => $id_intern,
                'value' => $value,
                'desiredPosition' => isset($row['desiredPosition']) ? intval($row['desiredPosition']) : null,
                'source' => 'local_fallback',
                'isMain' => !empty($row['isMain']) || !empty($row['is_main']) ? 1 : 0,
                'secondKeyword' => trim((string) ($row['secondKeyword'] ?? $row['second_keyword'] ?? '')),
                'secondaryKeywords' => isset($row['secondaryKeywords']) && is_array($row['secondaryKeywords'])
                    ? array_values(array_filter(array_map('strval', $row['secondaryKeywords'])))
                    : array(),
                'entityKey' => trim((string) ($row['entityKey'] ?? $row['entity_key'] ?? '')),
                'mappingSource' => trim((string) ($row['mappingSource'] ?? $row['mapping_source'] ?? '')),
                'confidenceScore' => isset($row['confidenceScore']) && is_numeric($row['confidenceScore']) ? floatval($row['confidenceScore']) : (isset($row['confidence_score']) && is_numeric($row['confidence_score']) ? floatval($row['confidence_score']) : null),
                'intentType' => trim((string) ($row['intentType'] ?? $row['intent_type'] ?? '')),
                'targetUrl' => trim((string) ($row['targetUrl'] ?? $row['target_url'] ?? '')),
                'updatedAt' => $row['updatedAt'] ?? current_time('mysql'),
            );
        }
        return $normalized;
    }

    private function save_keyword_discovery_local_associations($project_id, $rows) {
        $key = $this->get_keyword_discovery_local_associations_option_key($project_id);
        update_option($key, array_values($rows), false);
        $this->sync_keyword_discovery_associations_batch_to_entity_meta($rows);
        $this->mark_keyword_discovery_entity_meta_synced($project_id);
    }

    private function upsert_keyword_discovery_local_association($project_id, $keyword, $associated_on, $id_intern, $value) {
        $rows = $this->get_keyword_discovery_local_associations($project_id);
        $keyword_norm = strtolower(trim((string) $keyword));
        $associated_norm = strtolower(trim((string) $associated_on));
        $found_index = -1;
        foreach ($rows as $idx => $row) {
            $row_keyword = strtolower(trim((string) ($row['keyword'] ?? '')));
            $row_assoc = strtolower(trim((string) ($row['associatedOn'] ?? '')));
            $row_id_intern = intval($row['idIntern'] ?? 0);
            if ($row_keyword === $keyword_norm && $row_assoc === $associated_norm && $row_id_intern === intval($id_intern)) {
                $found_index = $idx;
                break;
            }
        }

        $new_row = array(
            'id' => $this->build_keyword_discovery_stable_local_id($keyword_norm, $associated_norm, intval($id_intern)),
            'projectId' => intval($project_id),
            'keyword' => trim((string) $keyword),
            'associatedOn' => trim((string) $associated_on),
            'idIntern' => intval($id_intern),
            'value' => trim((string) $value),
            'source' => 'local_fallback',
            'updatedAt' => current_time('mysql'),
        );

        if ($found_index >= 0) {
            $rows[$found_index] = array_merge($rows[$found_index], $new_row);
        } else {
            $rows[] = $new_row;
        }

        $this->save_keyword_discovery_local_associations($project_id, $rows);
        $this->sync_keyword_discovery_association_to_entity_meta($new_row);
        return $new_row;
    }

    private function update_keyword_discovery_local_association_desired_position($project_id, $keyword, $associated_on, $id_intern, $desired_position) {
        $rows = $this->get_keyword_discovery_local_associations($project_id);
        $keyword_norm = strtolower(trim((string) $keyword));
        $associated_norm = strtolower(trim((string) $associated_on));
        $desired_position = intval($desired_position);
        foreach ($rows as $idx => $row) {
            $row_keyword = strtolower(trim((string) ($row['keyword'] ?? '')));
            $row_assoc = strtolower(trim((string) ($row['associatedOn'] ?? $row['associated_on'] ?? '')));
            $row_id_intern = intval($row['idIntern'] ?? $row['id_intern'] ?? 0);
            if ($row_keyword !== $keyword_norm || $row_assoc !== $associated_norm || $row_id_intern !== intval($id_intern)) {
                continue;
            }

            $rows[$idx]['desiredPosition'] = $desired_position > 0 ? $desired_position : null;
            $rows[$idx]['updatedAt'] = current_time('mysql');
            $this->save_keyword_discovery_local_associations($project_id, $rows);
            $this->sync_keyword_discovery_association_to_entity_meta($rows[$idx]);
            return $rows[$idx];
        }

        return null;
    }

    private function find_keyword_discovery_local_association($project_id, $keyword, $associated_on = '', $id_intern = 0) {
        $rows = $this->get_keyword_discovery_local_associations($project_id);
        $keyword_norm = strtolower(trim((string) $keyword));
        $associated_norm = strtolower(trim((string) $associated_on));
        $id_intern = intval($id_intern);
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $row_keyword = strtolower(trim((string) ($row['keyword'] ?? '')));
            if ($row_keyword !== $keyword_norm) {
                continue;
            }
            if ($associated_norm !== '' && strtolower(trim((string) ($row['associatedOn'] ?? $row['associated_on'] ?? ''))) !== $associated_norm) {
                continue;
            }
            if ($id_intern > 0 && intval($row['idIntern'] ?? $row['id_intern'] ?? 0) !== $id_intern) {
                continue;
            }
            return $row;
        }
        return null;
    }

    private function sync_keyword_discovery_associations_batch_to_entity_meta($rows) {
        if (!is_array($rows) || empty($rows)) {
            return;
        }
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $this->sync_keyword_discovery_association_to_entity_meta($row);
        }
    }

    private function sync_keyword_discovery_association_to_entity_meta($row) {
        if (!is_array($row)) {
            return;
        }
        $keyword = trim((string) ($row['keyword'] ?? ''));
        if ($keyword === '') {
            return;
        }

        $associated_on = (string) ($row['associatedOn'] ?? $row['associated_on'] ?? '');
        $id_intern = intval($row['idIntern'] ?? $row['id_intern'] ?? 0);
        if ($id_intern <= 0 || $associated_on === '') {
            return;
        }

        $target = $this->resolve_keyword_discovery_entity_target($associated_on, $id_intern);
        if (!is_array($target)) {
            return;
        }

        $kind = (string) ($target['kind'] ?? '');
        $entity_id = intval($target['id'] ?? 0);
        if (($kind !== 'post' && $kind !== 'term') || $entity_id <= 0) {
            return;
        }

        $keyword_data = $this->get_keyword_discovery_entity_keyword_data($kind, $entity_id);
        if (!is_array($keyword_data)) {
            $keyword_data = array();
        }

        $main_keyword = trim((string) ($keyword_data['keyword'] ?? $keyword_data['aysa_keyword'] ?? ''));
        $secondary_raw = (string) ($keyword_data['second_keyword'] ?? $keyword_data['aysa_secondary_keywords'] ?? '');
        $secondary = $this->parse_keyword_discovery_secondary_keywords($secondary_raw);
        $incoming_norm = strtolower($keyword);

        if ($main_keyword === '') {
            $main_keyword = $keyword;
        } else {
            $main_norm = strtolower($main_keyword);
            if ($main_norm !== $incoming_norm) {
                $seen_secondary = array();
                foreach ($secondary as $sec) {
                    $seen_secondary[strtolower($sec)] = true;
                }
                if (!isset($seen_secondary[$incoming_norm])) {
                    $secondary[] = $keyword;
                }
            }
        }

        $secondary = $this->parse_keyword_discovery_secondary_keywords(implode(', ', $secondary));
        $secondary_string = implode(', ', $secondary);

        $keyword_data['keyword'] = $main_keyword;
        $keyword_data['aysa_keyword'] = $main_keyword;
        $keyword_data['second_keyword'] = $secondary_string;
        $keyword_data['aysa_secondary_keywords'] = $secondary_string;

        $this->update_keyword_discovery_entity_keyword_data($kind, $entity_id, $keyword_data);
    }

    private function resolve_keyword_discovery_entity_target($associated_on, $id_intern) {
        $id_intern = intval($id_intern);
        if ($id_intern <= 0) {
            return null;
        }

        $assoc = strtolower(trim((string) $associated_on));
        if ($assoc === '') {
            return null;
        }

        if (strpos($assoc, 'categor') !== false || strpos($assoc, 'term') !== false) {
            $term = get_term($id_intern);
            if ($term && !is_wp_error($term)) {
                return array('kind' => 'term', 'id' => $id_intern);
            }
            return null;
        }

        if (strpos($assoc, 'post') !== false || strpos($assoc, 'page') !== false || strpos($assoc, 'product') !== false) {
            $post = get_post($id_intern);
            if ($post instanceof WP_Post) {
                return array('kind' => 'post', 'id' => $id_intern);
            }
            return null;
        }

        $post = get_post($id_intern);
        if ($post instanceof WP_Post) {
            return array('kind' => 'post', 'id' => $id_intern);
        }
        $term = get_term($id_intern);
        if ($term && !is_wp_error($term)) {
            return array('kind' => 'term', 'id' => $id_intern);
        }

        return null;
    }

    private function get_keyword_discovery_entity_keyword_data($kind, $entity_id) {
        if ($kind === 'term') {
            $data = get_term_meta($entity_id, 'aysa_keyword_data', true);
            return is_array($data) ? $data : array();
        }
        $data = get_post_meta($entity_id, 'aysa_keyword_data', true);
        return is_array($data) ? $data : array();
    }

    private function update_keyword_discovery_entity_keyword_data($kind, $entity_id, $keyword_data) {
        if ($kind === 'term') {
            update_term_meta($entity_id, 'aysa_keyword_data', $keyword_data);
            return;
        }
        update_post_meta($entity_id, 'aysa_keyword_data', $keyword_data);
    }

    private function parse_keyword_discovery_secondary_keywords($raw_secondary) {
        $raw_secondary = trim((string) $raw_secondary);
        if ($raw_secondary === '') {
            return array();
        }

        $parts = array_map('trim', explode(',', $raw_secondary));
        $parts = array_filter($parts, function($value) {
            return $value !== '';
        });

        $deduped = array();
        $seen = array();
        foreach ($parts as $part) {
            $key = strtolower($part);
            if (isset($seen[$key])) {
                continue;
            }
            $seen[$key] = true;
            $deduped[] = $part;
        }
        return $deduped;
    }

    private function normalize_keyword_discovery_entity_type($entity_type) {
        $entity_type = strtolower(trim((string) $entity_type));
        return match ($entity_type) {
            'page', 'post', 'article', 'product', 'produs' => 'post',
            'category', 'categorie', 'term' => 'term',
            default => '',
        };
    }

    private function resolve_keyword_discovery_entity_from_payload($entity_type, $entity_id, $entity_label = '') {
        $kind = $this->normalize_keyword_discovery_entity_type($entity_type);
        $entity_id = intval($entity_id);
        $entity_label = trim((string) $entity_label);

        if ($entity_id > 0 && $kind !== '') {
            if ($kind === 'post') {
                $post = get_post($entity_id);
                if ($post instanceof WP_Post) {
                    return array(
                        'kind' => 'post',
                        'id' => $entity_id,
                        'associated_on' => $post->post_type === 'product' ? 'product' : 'page',
                        'label' => get_the_title($entity_id),
                    );
                }
            } else {
                $term = get_term($entity_id);
                if ($term && !is_wp_error($term)) {
                    return array(
                        'kind' => 'term',
                        'id' => $entity_id,
                        'associated_on' => 'category',
                        'label' => (string) $term->name,
                    );
                }
            }
        }

        if ($entity_label === '' || $kind === '') {
            return null;
        }

        if ($kind === 'post') {
            $posts = get_posts(array(
                'post_type' => array('page', 'post', 'product'),
                'post_status' => array('publish', 'draft', 'pending', 'private'),
                'posts_per_page' => 20,
                'title' => $entity_label,
                'suppress_filters' => false,
            ));
            foreach ($posts as $post) {
                if (!$post instanceof WP_Post) {
                    continue;
                }
                if (strcasecmp(trim((string) $post->post_title), $entity_label) === 0) {
                    return array(
                        'kind' => 'post',
                        'id' => intval($post->ID),
                        'associated_on' => $post->post_type === 'product' ? 'product' : ($post->post_type === 'post' ? 'article' : 'page'),
                        'label' => trim((string) $post->post_title),
                    );
                }
            }
        }

        if ($kind === 'term') {
            $terms = get_terms(array(
                'taxonomy' => array('category', 'product_cat'),
                'hide_empty' => false,
                'name' => $entity_label,
                'number' => 20,
            ));
            if (!is_wp_error($terms)) {
                foreach ($terms as $term) {
                    if (!($term instanceof WP_Term)) {
                        continue;
                    }
                    if (strcasecmp(trim((string) $term->name), $entity_label) === 0) {
                        return array(
                            'kind' => 'term',
                            'id' => intval($term->term_id),
                            'associated_on' => 'category',
                            'label' => trim((string) $term->name),
                        );
                    }
                }
            }
        }

        return null;
    }

    private function aysa_adjustments_execute_keyword_association_upsert($project_id, $payload, $language) {
        $keyword = trim((string) ($payload['keyword'] ?? ''));
        $entity = $this->resolve_keyword_discovery_entity_from_payload(
            (string) ($payload['entity_type'] ?? ''),
            intval($payload['entity_id'] ?? 0),
            (string) ($payload['entity_label'] ?? '')
        );

        if ($keyword === '' || !is_array($entity)) {
            return array(
                'module' => 'keyword_research',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I need both the exact keyword and the target entity before I can save the mapping.',
                    'Am nevoie atat de keywordul exact, cat si de entitatea tinta inainte sa pot salva maparea.',
                    'Трябват ми и точният keyword, и целевата entitate, преди да мога да запазя mapping-а.'
                ),
                'ui_action' => 'open_artifact_matches',
            );
        }

        $associated_on = (string) ($entity['associated_on'] ?? '');
        $id_intern = intval($entity['id'] ?? 0);
        $label = trim((string) ($entity['label'] ?? ''));
        if ($associated_on === '' || $id_intern <= 0 || $label === '') {
            return array(
                'module' => 'keyword_research',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I could not resolve the target entity well enough to save the mapping.',
                    'Nu am reusit sa rezolv suficient de bine entitatea tinta ca sa salvez maparea.',
                    'Не успях да разпозная достатъчно добре целевата entitate, за да запазя mapping-а.'
                ),
                'ui_action' => 'open_artifact_matches',
            );
        }

        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }

        $payload_remote = array(
            'projectId' => intval($project_id),
            'keyword' => $keyword,
            'associatedOn' => $associated_on,
            'idIntern' => $id_intern,
            'value' => $label,
        );

        try {
            $auth_manager = Aysa_Auth_Manager::get_instance();
            $raw = $auth_manager->make_authenticated_request('api/Association/upsert', 'PUT', $payload_remote);
            $local_assoc = $this->upsert_keyword_discovery_local_association($project_id, $keyword, $associated_on, $id_intern, $label);
            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_adjustments_keyword_association_upsert',
                'adjustments_keyword_association_upsert',
                array(
                    'module' => 'keyword_discovery',
                    'source' => is_array($raw) ? 'adjustments_remote_with_local_mirror' : 'adjustments_local_fallback',
                    'keyword' => $keyword,
                    'associated_on' => $associated_on,
                ),
                5,
                'api/Association/upsert'
            );

            return array(
                'module' => 'keyword_research',
                'assistant_message' => match ($language) {
                    'ro' => 'Am mapat keywordul "' . $keyword . '" la "' . $label . '". Asocierea este acum salvata si sincronizata local pentru Keyword Discovery.',
                    'bg' => 'Свързах keyword-а "' . $keyword . '" към "' . $label . '". Свързването вече е запазено и синхронизирано локално за Keyword Discovery.',
                    default => 'I mapped the keyword "' . $keyword . '" to "' . $label . '". The association is now saved and mirrored locally for Keyword Discovery.',
                },
                'result' => array(
                    'keyword' => $keyword,
                    'association' => is_array($raw) ? $raw : $local_assoc,
                    'local_association' => $local_assoc,
                ),
                'ui_action' => 'open_artifact_matches',
            );
        } catch (Exception $e) {
            $local_assoc = $this->upsert_keyword_discovery_local_association($project_id, $keyword, $associated_on, $id_intern, $label);
            return array(
                'module' => 'keyword_research',
                'assistant_message' => match ($language) {
                    'ro' => 'Am salvat local maparea pentru "' . $keyword . '" la "' . $label . '". API-ul remote nu a raspuns, dar Keyword Discovery vede acum asocierea locala.',
                    'bg' => 'Запазих локално mapping-а за "' . $keyword . '" към "' . $label . '". Remote API не отговори, но Keyword Discovery вече вижда локалното свързване.',
                    default => 'I saved the mapping locally for "' . $keyword . '" to "' . $label . '". The remote API did not respond, but Keyword Discovery can already see the local association.',
                },
                'result' => array(
                    'keyword' => $keyword,
                    'local_association' => $local_assoc,
                    'warning' => $e->getMessage(),
                ),
                'ui_action' => 'open_artifact_matches',
            );
        }
    }

    private function aysa_adjustments_execute_keyword_target_position_set($project_id, $payload, $language) {
        $keyword = trim((string) ($payload['keyword'] ?? ''));
        $target_position = intval($payload['target_position'] ?? 0);
        $entity = $this->resolve_keyword_discovery_entity_from_payload(
            (string) ($payload['entity_type'] ?? ''),
            intval($payload['entity_id'] ?? 0),
            (string) ($payload['entity_label'] ?? '')
        );

        if ($keyword === '' || $target_position <= 0) {
            return array(
                'module' => 'keyword_research',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I need both the exact keyword and the target position before I can update the desired position.',
                    'Am nevoie atat de keywordul exact, cat si de targetul dorit inainte sa pot actualiza pozitia.',
                    'Трябват ми и точният keyword, и target позицията, преди да мога да обновя desired position.'
                ),
                'ui_action' => 'open_artifact_matches',
            );
        }

        $association = null;
        if (is_array($entity)) {
            $association = $this->find_keyword_discovery_local_association(
                $project_id,
                $keyword,
                (string) ($entity['associated_on'] ?? ''),
                intval($entity['id'] ?? 0)
            );
        }
        if (!is_array($association)) {
            $association = $this->find_keyword_discovery_local_association($project_id, $keyword);
        }
        if (!is_array($association)) {
            return array(
                'module' => 'keyword_research',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I could not find a saved association for that keyword yet. First map the keyword to a page, product, category, or article, then I can set the desired position.',
                    'Nu am gasit inca o asociere salvata pentru acel keyword. Mai intai mapeaza keywordul pe o pagina, produs, categorie sau articol, apoi pot seta pozitia dorita.',
                    'Все още не намерих запазено свързване за този keyword. Първо вържи keyword-а към страница, продукт, категория или статия и после мога да задам desired position.'
                ),
                'ui_action' => 'open_artifact_matches',
            );
        }

        $associated_on = (string) ($association['associatedOn'] ?? $association['associated_on'] ?? '');
        $id_intern = intval($association['idIntern'] ?? $association['id_intern'] ?? 0);
        $association_id = intval($association['id'] ?? 0);
        $label = trim((string) ($association['value'] ?? ''));
        $local_assoc = $this->update_keyword_discovery_local_association_desired_position($project_id, $keyword, $associated_on, $id_intern, $target_position);

        if ($association_id > 0 && !class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }

        $remote_warning = '';
        if ($association_id > 0) {
            try {
                $auth_manager = Aysa_Auth_Manager::get_instance();
                $auth_manager->make_authenticated_request(
                    'api/Association/' . rawurlencode((string) $association_id) . '/desired-position',
                    'PATCH',
                    array('DesiredPosition' => $target_position)
                );
                $this->aysa_wallet_track_action_usage(
                    $project_id,
                    'aysa_adjustments_keyword_target_position_set',
                    'adjustments_keyword_target_position_set',
                    array(
                        'module' => 'keyword_discovery',
                        'source' => 'adjustments_remote_with_local_mirror',
                        'keyword' => $keyword,
                        'target_position' => $target_position,
                    ),
                    2,
                    'api/Association/{id}/desired-position'
                );
            } catch (Exception $e) {
                $remote_warning = $e->getMessage();
            }
        }

        return array(
            'module' => 'keyword_research',
            'assistant_message' => match ($language) {
                'ro' => 'Am setat targetul pentru "' . $keyword . '" la pozitia ' . $target_position . ($label !== '' ? ' pe "' . $label . '"' : '') . '. Folosesc acest target in discutia despre efort, refresh si cost.',
                'bg' => 'Зададох target-а за "' . $keyword . '" на позиция ' . $target_position . ($label !== '' ? ' върху "' . $label . '"' : '') . '. Използвам този target в разговора за усилие, refresh и разход.',
                default => 'I set the target for "' . $keyword . '" to position ' . $target_position . ($label !== '' ? ' on "' . $label . '"' : '') . '. I now use that target in the effort, refresh, and cost discussion.',
            },
            'result' => array(
                'keyword' => $keyword,
                'target_position' => $target_position,
                'association' => $local_assoc ?: $association,
                'warning' => $remote_warning,
            ),
            'ui_action' => 'open_artifact_matches',
        );
    }

    private function aysa_adjustments_execute_keyword_primary_set($project_id, $payload, $language) {
        $keyword = trim((string) ($payload['keyword'] ?? ''));
        $entity = $this->resolve_keyword_discovery_entity_from_payload(
            (string) ($payload['entity_type'] ?? ''),
            intval($payload['entity_id'] ?? 0),
            (string) ($payload['entity_label'] ?? '')
        );
        if ($keyword === '' || !is_array($entity)) {
            return array(
                'module' => 'keyword_research',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I need both the exact keyword and the target entity before I can mark the primary keyword.',
                    'Am nevoie atat de keywordul exact, cat si de entitatea tinta inainte sa pot marca keywordul principal.',
                    'Трябват ми и точният keyword, и целевата entitate, преди да мога да маркирам primary keyword.'
                ),
                'ui_action' => 'open_keyword_discovery',
            );
        }

        $kind = (string) ($entity['kind'] ?? '');
        $entity_id = intval($entity['id'] ?? 0);
        $label = trim((string) ($entity['label'] ?? ''));
        if (($kind !== 'post' && $kind !== 'term') || $entity_id <= 0) {
            return array(
                'module' => 'keyword_research',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I could not resolve the entity well enough to set the primary keyword.',
                    'Nu am reusit sa rezolv suficient de bine entitatea ca sa setez keywordul principal.',
                    'Не успях да разпозная достатъчно добре entitate-та, за да задам primary keyword.'
                ),
                'ui_action' => 'open_keyword_discovery',
            );
        }

        $keyword_data = $this->get_keyword_discovery_entity_keyword_data($kind, $entity_id);
        $current_main = trim((string) ($keyword_data['keyword'] ?? $keyword_data['aysa_keyword'] ?? ''));
        $secondary = $this->parse_keyword_discovery_secondary_keywords((string) ($keyword_data['second_keyword'] ?? $keyword_data['aysa_secondary_keywords'] ?? ''));

        $secondary = array_values(array_filter($secondary, function($item) use ($keyword) {
            return strtolower(trim((string) $item)) !== strtolower($keyword);
        }));

        if ($current_main !== '' && strtolower($current_main) !== strtolower($keyword)) {
            $secondary[] = $current_main;
        }

        $secondary = $this->parse_keyword_discovery_secondary_keywords(implode(', ', $secondary));
        $secondary_string = implode(', ', $secondary);

        $keyword_data['keyword'] = $keyword;
        $keyword_data['aysa_keyword'] = $keyword;
        $keyword_data['second_keyword'] = $secondary_string;
        $keyword_data['aysa_secondary_keywords'] = $secondary_string;
        $this->update_keyword_discovery_entity_keyword_data($kind, $entity_id, $keyword_data);

        return array(
            'module' => 'keyword_research',
            'assistant_message' => match ($language) {
                'ro' => 'Am setat "' . $keyword . '" ca keyword principal pentru "' . $label . '". Restul raman keyworduri de suport pe aceeasi entitate.',
                'bg' => 'Зададох "' . $keyword . '" като primary keyword за "' . $label . '". Останалите остават supporting keywords за същата entitate.',
                default => 'I set "' . $keyword . '" as the primary keyword for "' . $label . '". The rest stay as supporting keywords on the same entity.',
            },
            'result' => array(
                'keyword' => $keyword,
                'entity' => $entity,
                'keyword_data' => $keyword_data,
            ),
            'ui_action' => 'open_keyword_discovery',
        );
    }

    private function aysa_adjustments_execute_keyword_supporting_add($project_id, $payload, $language) {
        $keyword = trim((string) ($payload['keyword'] ?? ''));
        $entity = $this->resolve_keyword_discovery_entity_from_payload(
            (string) ($payload['entity_type'] ?? ''),
            intval($payload['entity_id'] ?? 0),
            (string) ($payload['entity_label'] ?? '')
        );
        if ($keyword === '' || !is_array($entity)) {
            return array(
                'module' => 'keyword_research',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I need both the exact keyword and the target entity before I can add a supporting keyword.',
                    'Am nevoie atat de keywordul exact, cat si de entitatea tinta inainte sa pot adauga un keyword de suport.',
                    'Трябват ми и точният keyword, и целевата entitate, преди да мога да добавя supporting keyword.'
                ),
                'ui_action' => 'open_keyword_discovery',
            );
        }

        $kind = (string) ($entity['kind'] ?? '');
        $entity_id = intval($entity['id'] ?? 0);
        $label = trim((string) ($entity['label'] ?? ''));
        if (($kind !== 'post' && $kind !== 'term') || $entity_id <= 0) {
            return array(
                'module' => 'keyword_research',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I could not resolve the entity well enough to add the supporting keyword.',
                    'Nu am reusit sa rezolv suficient de bine entitatea ca sa adaug keywordul de suport.',
                    'Не успях да разпозная достатъчно добре entitate-та, за да добавя supporting keyword.'
                ),
                'ui_action' => 'open_keyword_discovery',
            );
        }

        $keyword_data = $this->get_keyword_discovery_entity_keyword_data($kind, $entity_id);
        $current_main = trim((string) ($keyword_data['keyword'] ?? $keyword_data['aysa_keyword'] ?? ''));
        $secondary = $this->parse_keyword_discovery_secondary_keywords((string) ($keyword_data['second_keyword'] ?? $keyword_data['aysa_secondary_keywords'] ?? ''));

        if ($current_main === '') {
            $keyword_data['keyword'] = $keyword;
            $keyword_data['aysa_keyword'] = $keyword;
        } elseif (strtolower($current_main) !== strtolower($keyword)) {
            $secondary[] = $keyword;
        }

        $secondary = $this->parse_keyword_discovery_secondary_keywords(implode(', ', $secondary));
        $secondary_string = implode(', ', $secondary);
        $keyword_data['second_keyword'] = $secondary_string;
        $keyword_data['aysa_secondary_keywords'] = $secondary_string;
        $this->update_keyword_discovery_entity_keyword_data($kind, $entity_id, $keyword_data);

        return array(
            'module' => 'keyword_research',
            'assistant_message' => match ($language) {
                'ro' => 'Am adaugat "' . $keyword . '" ca keyword de suport pentru "' . $label . '".',
                'bg' => 'Добавих "' . $keyword . '" като supporting keyword за "' . $label . '".',
                default => 'I added "' . $keyword . '" as a supporting keyword for "' . $label . '".',
            },
            'result' => array(
                'keyword' => $keyword,
                'entity' => $entity,
                'keyword_data' => $keyword_data,
            ),
            'ui_action' => 'open_keyword_discovery',
        );
    }

    private function aysa_adjustments_execute_keyword_cluster_map_to_entity($project_id, $payload, $language) {
        $cluster_keywords = isset($payload['cluster_keywords']) && is_array($payload['cluster_keywords'])
            ? array_values(array_filter(array_map('strval', $payload['cluster_keywords'])))
            : array();
        $cluster_keywords = array_values(array_unique(array_filter(array_map('trim', $cluster_keywords))));
        $entity_type = (string) ($payload['entity_type'] ?? '');
        $entity_id = intval($payload['entity_id'] ?? 0);
        $entity_label = (string) ($payload['entity_label'] ?? '');

        if (count($cluster_keywords) < 2) {
            return array(
                'module' => 'keyword_research',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I need at least two keywords in the cluster before I can map the cluster to one entity.',
                    'Am nevoie de cel putin doua keyworduri in cluster inainte sa pot mapa clusterul pe o singura entitate.',
                    'Трябват ми поне два keyword-а в cluster-а, преди да мога да го свържа с една entitate.'
                ),
                'ui_action' => 'open_keyword_discovery',
            );
        }

        $created = 0;
        $failed = 0;
        foreach ($cluster_keywords as $keyword) {
            $result = $this->aysa_adjustments_execute_keyword_association_upsert($project_id, array(
                'keyword' => $keyword,
                'entity_type' => $entity_type,
                'entity_id' => $entity_id,
                'entity_label' => $entity_label,
            ), $language);
            if (!empty($result['result'])) {
                $created++;
            } else {
                $failed++;
            }
        }

        return array(
            'module' => 'keyword_research',
            'assistant_message' => match ($language) {
                'ro' => 'Am mapat clusterul pe entitatea aleasa. Keyworduri procesate: ' . $created . ', esecuri: ' . $failed . '. Dupa asta putem stabili si keywordul principal plus termenii de suport.',
                'bg' => 'Свързах cluster-а към избраната entitate. Обработени keyword-и: ' . $created . ', неуспехи: ' . $failed . '. След това можем да зададем и primary keyword плюс supporting terms.',
                default => 'I mapped the cluster to the chosen entity. Processed keywords: ' . $created . ', failures: ' . $failed . '. After that we can also set the primary keyword and the supporting terms.',
            },
            'result' => array(
                'cluster_keywords' => $cluster_keywords,
                'processed' => $created,
                'failed' => $failed,
            ),
            'ui_action' => 'open_keyword_discovery',
        );
    }

    private function aysa_adjustments_execute_keyword_tracking_update($project_id, $payload, $language) {
        $keywords = isset($payload['keywords']) && is_array($payload['keywords'])
            ? array_values(array_filter(array_map('strval', $payload['keywords'])))
            : array();
        $keywords = array_values(array_unique(array_filter(array_map('trim', $keywords))));
        $tracking_mode = trim((string) ($payload['tracking_mode'] ?? 'balanced'));
        $target_position = intval($payload['target_position'] ?? 0);

        if ($keywords === []) {
            return array(
                'module' => 'keyword_research',
                'assistant_message' => $this->aysa_adjustments_tr(
                    $language,
                    'I need at least one keyword before I can save the tracking plan.',
                    'Am nevoie de cel putin un keyword inainte sa pot salva planul de tracking.',
                    'Трябва ми поне един keyword, преди да мога да запазя tracking плана.'
                ),
                'ui_action' => 'open_keyword_discovery',
            );
        }

        $mode = in_array($tracking_mode, array('conservative', 'balanced', 'aggressive'), true)
            ? $tracking_mode
            : 'balanced';
        $cost_band = match ($mode) {
            'aggressive' => 'high',
            'conservative' => 'low',
            default => 'medium',
        };
        $refresh_frequency = match ($mode) {
            'aggressive' => 'weekly',
            'conservative' => 'monthly',
            default => 'biweekly',
        };
        $content_intensity = match ($mode) {
            'aggressive' => 'high',
            'conservative' => 'low',
            default => 'medium',
        };

        $plan = array(
            'project_id' => intval($project_id),
            'updated_at' => current_time('mysql'),
            'tracking_mode' => $mode,
            'target_position' => $target_position > 0 ? $target_position : null,
            'refresh_frequency' => $refresh_frequency,
            'content_intensity' => $content_intensity,
            'cost_band' => $cost_band,
            'keywords' => array_map(function($keyword) use ($mode, $target_position, $refresh_frequency, $content_intensity, $cost_band) {
                return array(
                    'keyword' => $keyword,
                    'tracking_mode' => $mode,
                    'target_position' => $target_position > 0 ? $target_position : null,
                    'refresh_frequency' => $refresh_frequency,
                    'content_intensity' => $content_intensity,
                    'cost_band' => $cost_band,
                );
            }, $keywords),
        );
        $this->aysa_save_keyword_tracking_plan($project_id, $plan);

        return array(
            'module' => 'keyword_research',
            'assistant_message' => match ($language) {
                'ro' => 'Am salvat planul de tracking pentru ' . count($keywords) . ' keyworduri in modul `' . $mode . '`. Asta seteaza si discutia despre refresh, continut si cost in jurul acestor keyworduri.',
                'bg' => 'Запазих tracking plan-а за ' . count($keywords) . ' keyword-а в режим `' . $mode . '`. Това задава и разговора за refresh, съдържание и разход около тези keyword-и.',
                default => 'I saved the tracking plan for ' . count($keywords) . ' keywords in `' . $mode . '` mode. That now sets the refresh, content, and cost discussion around those keywords.',
            },
            'result' => $plan,
            'ui_action' => 'open_keyword_discovery',
        );
    }

    private function get_keyword_discovery_entity_meta_sync_option_key($project_id) {
        return 'aysa_keyword_discovery_entity_meta_synced_' . intval($project_id);
    }

    private function is_keyword_discovery_entity_meta_synced($project_id) {
        return !empty(get_option($this->get_keyword_discovery_entity_meta_sync_option_key($project_id), false));
    }

    private function mark_keyword_discovery_entity_meta_synced($project_id) {
        update_option($this->get_keyword_discovery_entity_meta_sync_option_key($project_id), 1, false);
    }

    private function build_keyword_discovery_stable_local_id($keyword, $associated_on, $id_intern) {
        $signature = strtolower(trim((string) $keyword)) . '|' .
            strtolower(trim((string) $associated_on)) . '|' .
            intval($id_intern);
        $hash = sprintf('%u', crc32($signature));
        return intval($hash);
    }

    private function merge_keyword_discovery_associations($primary_rows, $secondary_rows) {
        $merged = array();
        $seen = array();
        $append = function($rows) use (&$merged, &$seen) {
            if (!is_array($rows)) {
                return;
            }
            foreach ($rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $keyword = strtolower(trim((string) ($row['keyword'] ?? '')));
                $associated_on = strtolower(trim((string) ($row['associatedOn'] ?? $row['associated_on'] ?? '')));
                $id_intern = intval($row['idIntern'] ?? $row['id_intern'] ?? 0);
                if ($keyword === '' || $associated_on === '' || $id_intern <= 0) {
                    continue;
                }
                $sig = $keyword . '|' . $associated_on . '|' . $id_intern;
                if (isset($seen[$sig])) {
                    continue;
                }
                $seen[$sig] = true;
                $merged[] = $row;
            }
        };
        $append($primary_rows);
        $append($secondary_rows);
        return $merged;
    }

    public function handle_keyword_discovery_get_keywords_and_competitors() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }

        try {
            $auth_manager = Aysa_Auth_Manager::get_instance();
            $raw = $auth_manager->make_authenticated_request(
                'api/KeywordsAndCompetitors/ByProject/' . rawurlencode((string) $project_id),
                'GET'
            );

            $payload = is_array($raw) ? $raw : array();
            if (isset($payload['data']) && is_array($payload['data'])) {
                $payload = $payload['data'];
            }

            $keywords = array();
            if (isset($payload['keywords']) && is_array($payload['keywords'])) {
                $keywords = array_values($payload['keywords']);
            } elseif (isset($payload['Keywords']) && is_array($payload['Keywords'])) {
                $keywords = array_values($payload['Keywords']);
            }

            // Fallback: when remote target keywords are empty, use locally saved seed keywords
            // from Setup SEO Profile > Competition step so users always see planning anchors.
            if (empty($keywords)) {
                $seed_data = $this->aysa_keyword_strategy_extract_seed_data(get_current_user_id());
                if (is_array($seed_data) && !empty($seed_data['seed_keywords']) && is_array($seed_data['seed_keywords'])) {
                    $keywords = array_values(array_filter(array_map(function($kw) {
                        return trim((string) $kw);
                    }, $seed_data['seed_keywords']), function($kw) {
                        return $kw !== '';
                    }));
                }
            }

            // Secondary fallback: if current user has no saved competition data (common when
            // switching admin accounts), reuse the most recent non-empty competition option.
            if (empty($keywords)) {
                global $wpdb;
                $like = $wpdb->esc_like('aysa_wizard_competition_') . '%';
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $rows = $wpdb->get_results(
                    $wpdb->prepare(
                        "SELECT option_value FROM {$wpdb->options} WHERE option_name LIKE %s ORDER BY option_id DESC LIMIT 25",
                        $like
                    ),
                    ARRAY_A
                );

                if (is_array($rows)) {
                    foreach ($rows as $row) {
                        $raw_option = isset($row['option_value']) ? maybe_unserialize($row['option_value']) : array();
                        if (!is_array($raw_option) || empty($raw_option)) {
                            continue;
                        }

                        $candidate = array();
                        foreach ($raw_option as $k => $v) {
                            if (!preg_match('/^keyword\d+$/', (string) $k)) {
                                continue;
                            }
                            $kw = trim((string) $v);
                            if ($kw !== '') {
                                $candidate[] = $kw;
                            }
                        }

                        $candidate = array_values(array_unique($candidate));
                        if (!empty($candidate)) {
                            $keywords = $candidate;
                            break;
                        }
                    }
                }
            }

            wp_send_json_success(array(
                'project_id' => $project_id,
                'payload' => $payload,
                'keywords' => $keywords,
            ));
        } catch (Exception $e) {
            wp_send_json_error('Failed to fetch keywords and competitors: ' . $e->getMessage());
        }
    }

    private function aysa_research_async_supported_tasks() {
        return array(
            'competitor_gap',
            'competitor_identification',
            'keyword_strategy_generate',
        );
    }

    private function aysa_research_async_status_key($project_id, $task) {
        return 'aysa_research_async_' . sanitize_key((string) $task) . '_' . intval($project_id);
    }

    private function aysa_research_async_get_status($project_id, $task) {
        $raw = get_option($this->aysa_research_async_status_key($project_id, $task), array());
        return is_array($raw) ? $raw : array();
    }

    private function aysa_research_async_save_status($project_id, $task, $status) {
        update_option($this->aysa_research_async_status_key($project_id, $task), is_array($status) ? $status : array(), false);
    }

    private function aysa_research_async_schedule_event($payload, $delay_seconds = 1) {
        if (!is_array($payload)) {
            return;
        }
        $timestamp = time() + max(1, intval($delay_seconds));
        wp_schedule_single_event($timestamp, 'aysa_research_async_event', array($payload));
        if (function_exists('spawn_cron')) {
            spawn_cron();
        }
    }

    private function aysa_research_async_prepare_competitor_gap_request($input) {
        $project_id = $this->aysa_resolve_installed_project_id(intval($input['project_id'] ?? 0));
        if ($project_id <= 0) {
            return new WP_Error('invalid_project', 'Invalid project id');
        }

        $force_refresh = !empty($input['force_refresh']) && intval($input['force_refresh']) === 1;
        $target = sanitize_text_field((string) ($input['target'] ?? ''));
        if ($target === '') {
            $target = (string) wp_parse_url(home_url('/'), PHP_URL_HOST);
        }
        $location_name = sanitize_text_field((string) ($input['location_name'] ?? 'Romania'));
        $language_name = sanitize_text_field((string) ($input['language_name'] ?? 'Romanian'));
        $limit = max(1, min(2000, intval($input['limit'] ?? 200)));

        $competitors_raw = $input['competitors'] ?? array();
        if (is_string($competitors_raw)) {
            $decoded = json_decode(wp_unslash($competitors_raw), true);
            $competitors_raw = is_array($decoded) ? $decoded : array();
        } elseif (is_array($competitors_raw)) {
            $competitors_raw = array_map('wp_unslash', $competitors_raw);
        } else {
            $competitors_raw = array();
        }

        $competitors = array();
        foreach ($competitors_raw as $competitor) {
            $value = trim(sanitize_text_field((string) $competitor));
            if ($value !== '') {
                $competitors[] = $value;
            }
        }
        $competitors = array_values(array_unique($competitors));

        $context = $this->aysa_monitoring_resolve_context($target);
        $resolved_target = trim((string) ($context['target'] ?? ''));
        if ($resolved_target === '') {
            $resolved_target = $target;
        }

        $normalized_competitors = array();
        foreach ($competitors as $competitor) {
            $host = $this->aysa_monitoring_extract_host((string) $competitor);
            if ($host === '' || $this->aysa_monitoring_is_local_host($host)) {
                if ($resolved_target !== '') {
                    $host = $resolved_target;
                }
            }
            if ($host !== '') {
                $normalized_competitors[] = $host;
            }
        }
        $normalized_competitors = array_values(array_unique($normalized_competitors));
        if (empty($normalized_competitors) && $resolved_target !== '') {
            $normalized_competitors[] = $resolved_target;
        }

        $gap_payload = array(
            'project_id' => $project_id,
            'target' => $resolved_target,
            'location_code' => intval($context['location_code'] ?? 2840),
            'language_code' => (string) ($context['language_code'] ?? 'ro'),
            'limit' => $limit,
            'competitors' => $normalized_competitors,
        );

        return array(
            'task' => 'competitor_gap',
            'project_id' => $project_id,
            'force_refresh' => $force_refresh,
            'target' => $target,
            'location_name' => $location_name,
            'language_name' => $language_name,
            'limit' => $limit,
            'competitors' => $competitors,
            'gap_payload' => $gap_payload,
            'request_hash' => md5(wp_json_encode($gap_payload)),
        );
    }

    private function aysa_research_async_get_competitor_gap_cached_result($request) {
        if (!is_array($request) || !empty($request['force_refresh'])) {
            return null;
        }
        $project_id = intval($request['project_id'] ?? 0);
        $gap_payload = isset($request['gap_payload']) && is_array($request['gap_payload']) ? $request['gap_payload'] : array();
        $cached = $this->aysa_competitor_gap_get_cached_payload($project_id);
        $cached_request = is_array($cached) && isset($cached['request']) && is_array($cached['request']) ? $cached['request'] : array();
        if (
            is_array($cached) &&
            !empty($cached['data']) &&
            is_array($cached['data']) &&
            $this->aysa_competitor_gap_requests_match($cached_request, $gap_payload)
        ) {
            $cached_data = $cached['data'];
            $cached_data['cached'] = true;
            $cached_data['cached_at'] = intval($cached['cached_at'] ?? 0);
            $cached_data['inKeywordResearch'] = $this->aysa_keyword_discovery_existing_keywords_map(
                $project_id,
                isset($cached_data['rankedKeywords']) && is_array($cached_data['rankedKeywords']) ? $cached_data['rankedKeywords'] : array()
            );
            return $cached_data;
        }

        return null;
    }

    private function aysa_research_async_execute_competitor_gap($request) {
        if (!is_array($request)) {
            return new WP_Error('invalid_request', 'Invalid competitor gap request.');
        }

        $project_id = intval($request['project_id'] ?? 0);
        $target = (string) ($request['target'] ?? '');
        $competitors = isset($request['competitors']) && is_array($request['competitors']) ? array_values($request['competitors']) : array();
        $gap_payload = isset($request['gap_payload']) && is_array($request['gap_payload']) ? $request['gap_payload'] : array();

        $cached = $this->aysa_research_async_get_competitor_gap_cached_result($request);
        if (is_array($cached)) {
            return $cached;
        }

        $broker_paths = array(
            '/v1/dataforseo/keywords/gap',
            '/v1/keywords/gap',
        );

        foreach ($broker_paths as $broker_path) {
            $broker_result = $this->aysa_broker_request('POST', $broker_path, $gap_payload, 180);
            if (is_wp_error($broker_result)) {
                continue;
            }

            if (is_array($broker_result) && !empty($broker_result['ok']) && isset($broker_result['rankedKeywords'])) {
                $this->aysa_competitor_gap_save_cached_payload($project_id, $gap_payload, $broker_result);
                $broker_result['cached'] = false;
                $broker_result['cached_at'] = time();
                $broker_result['inKeywordResearch'] = $this->aysa_keyword_discovery_existing_keywords_map(
                    $project_id,
                    isset($broker_result['rankedKeywords']) && is_array($broker_result['rankedKeywords']) ? $broker_result['rankedKeywords'] : array()
                );
                $this->aysa_wallet_track_action_usage(
                    $project_id,
                    'aysa_competitor_gap_keywords_gap',
                    'competitor_gap_keywords_gap',
                    array(
                        'module' => 'competitor_gap',
                        'source' => 'broker',
                        'broker_path' => $broker_path,
                        'target' => $target,
                        'competitors_count' => count($competitors),
                    ),
                    35,
                    (string) $broker_path
                );
                return $broker_result;
            }

            if (is_array($broker_result) && isset($broker_result['data']) && is_array($broker_result['data']) && isset($broker_result['data']['rankedKeywords'])) {
                $data = $broker_result['data'];
                $this->aysa_competitor_gap_save_cached_payload($project_id, $gap_payload, $data);
                $data['cached'] = false;
                $data['cached_at'] = time();
                $data['inKeywordResearch'] = $this->aysa_keyword_discovery_existing_keywords_map(
                    $project_id,
                    isset($data['rankedKeywords']) && is_array($data['rankedKeywords']) ? $data['rankedKeywords'] : array()
                );
                $this->aysa_wallet_track_action_usage(
                    $project_id,
                    'aysa_competitor_gap_keywords_gap',
                    'competitor_gap_keywords_gap',
                    array(
                        'module' => 'competitor_gap',
                        'source' => 'broker_data',
                        'broker_path' => $broker_path,
                        'target' => $target,
                        'competitors_count' => count($competitors),
                    ),
                    35,
                    (string) $broker_path
                );
                return $data;
            }
        }

        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }

        $query = array(
            'target' => $target,
            'locationName' => (string) ($request['location_name'] ?? 'Romania'),
            'languageName' => (string) ($request['language_name'] ?? 'Romanian'),
            'ProjectId' => $project_id,
            'limit' => intval($request['limit'] ?? 200),
        );
        if (!empty($competitors)) {
            $query['competitors'] = $competitors;
        }

        try {
            $auth_manager = Aysa_Auth_Manager::get_instance();
            $endpoint = 'api/keywordsgap/keywords-gap?' . http_build_query($query);
            $raw = $auth_manager->make_authenticated_request($endpoint, 'GET');
            if ($raw === false) {
                return new WP_Error('empty_response', 'Keywords gap endpoint returned empty response.');
            }

            if (is_string($raw)) {
                $decoded = json_decode($raw, true);
                if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                    $raw = $decoded;
                }
            }

            if (!is_array($raw)) {
                return new WP_Error('invalid_response', 'Invalid keywords gap response format.');
            }

            if (isset($raw['rankedKeywords']) && is_array($raw['rankedKeywords'])) {
                $this->aysa_competitor_gap_save_cached_payload($project_id, $gap_payload, $raw);
                $raw['cached'] = false;
                $raw['cached_at'] = time();
                $raw['inKeywordResearch'] = $this->aysa_keyword_discovery_existing_keywords_map(
                    $project_id,
                    $raw['rankedKeywords']
                );
            }
            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_competitor_gap_keywords_gap',
                'competitor_gap_keywords_gap',
                array(
                    'module' => 'competitor_gap',
                    'source' => 'legacy_remote',
                    'target' => $target,
                    'competitors_count' => count($competitors),
                ),
                35,
                '/v1/keywords/gap'
            );

            return $raw;
        } catch (Exception $e) {
            return new WP_Error('legacy_gap_failed', 'Failed to fetch keywords gap data: ' . $e->getMessage());
        }
    }

    private function aysa_research_async_prepare_competitor_identification_request($input) {
        $project_id = $this->aysa_resolve_installed_project_id(intval($input['project_id'] ?? 0));
        if ($project_id <= 0) {
            return new WP_Error('invalid_project', 'Invalid project id');
        }

        $force_refresh = !empty($input['force_refresh']) && intval($input['force_refresh']) === 1;
        $top_n = intval($input['top_n'] ?? 10);
        if ($top_n <= 0) {
            $top_n = 10;
        }
        $top_n = min($top_n, 20);

        $seed_user_id = intval($input['seed_user_id'] ?? get_current_user_id());
        $seed_data = $this->aysa_keyword_strategy_extract_seed_data($seed_user_id);
        $seed_keywords = isset($seed_data['seed_keywords']) && is_array($seed_data['seed_keywords']) ? array_values($seed_data['seed_keywords']) : array();
        if (empty($seed_keywords)) {
            $shared_user_id = intval(get_option('aysa_user_id', 0));
            if ($shared_user_id > 0 && $shared_user_id !== $seed_user_id) {
                $shared_seed_data = $this->aysa_keyword_strategy_extract_seed_data($shared_user_id);
                $shared_seed_keywords = isset($shared_seed_data['seed_keywords']) && is_array($shared_seed_data['seed_keywords'])
                    ? array_values($shared_seed_data['seed_keywords'])
                    : array();
                if (!empty($shared_seed_keywords)) {
                    $seed_keywords = $shared_seed_keywords;
                }
            }
        }
        $seed_keywords = array_values(array_filter(array_map(function($kw) {
            return trim((string) $kw);
        }, $seed_keywords), function($kw) {
            return $kw !== '';
        }));

        $configured_competitors = isset($seed_data['competitors']) && is_array($seed_data['competitors']) ? array_values($seed_data['competitors']) : array();
        if (empty($configured_competitors)) {
            $shared_user_id = intval(get_option('aysa_user_id', 0));
            if ($shared_user_id > 0 && $shared_user_id !== $seed_user_id) {
                $shared_seed_data = $this->aysa_keyword_strategy_extract_seed_data($shared_user_id);
                $shared_competitors = isset($shared_seed_data['competitors']) && is_array($shared_seed_data['competitors'])
                    ? array_values($shared_seed_data['competitors'])
                    : array();
                if (!empty($shared_competitors)) {
                    $configured_competitors = $shared_competitors;
                }
            }
        }
        $configured_competitors = array_values(array_unique(array_filter(array_map(function($domain) {
            return $this->aysa_monitoring_extract_host((string) $domain);
        }, $configured_competitors), function($domain) {
            return $domain !== '';
        })));

        if (empty($seed_keywords)) {
            return new WP_Error('missing_seeds', 'No seed keywords found in Setup SEO Profile.');
        }

        $home_host = $this->aysa_monitoring_extract_host((string) home_url('/'));
        $resolved_context = $this->aysa_monitoring_resolve_context($home_host);
        $location_code = intval($resolved_context['location_code'] ?? 2840);
        if ($location_code <= 0) {
            $location_code = 2840;
        }
        $language_code = strtolower(trim((string) ($resolved_context['language_code'] ?? 'ro')));
        if ($language_code === '') {
            $language_code = 'ro';
        }
        $target_host = trim((string) ($resolved_context['target'] ?? ''));
        $own_domains = array();
        if ($home_host !== '') {
            $own_domains[] = strtolower($home_host);
        }
        if ($target_host !== '') {
            $own_domains[] = strtolower($target_host);
        }
        $own_domains = array_values(array_unique(array_filter($own_domains)));

        $request_payload = array(
            'project_id' => $project_id,
            'seed_keywords' => array_values($seed_keywords),
            'active_competitors' => array_values($configured_competitors),
            'location_code' => $location_code,
            'language_code' => $language_code,
            'top_n' => $top_n,
            'own_domains' => $own_domains,
        );

        return array(
            'task' => 'competitor_identification',
            'project_id' => $project_id,
            'force_refresh' => $force_refresh,
            'top_n' => $top_n,
            'seed_keywords' => $seed_keywords,
            'request_payload' => $request_payload,
            'request_hash' => md5(wp_json_encode($request_payload)),
        );
    }

    private function aysa_research_async_get_competitor_identification_cached_result($request) {
        if (!is_array($request) || !empty($request['force_refresh'])) {
            return null;
        }
        $project_id = intval($request['project_id'] ?? 0);
        $request_payload = isset($request['request_payload']) && is_array($request['request_payload']) ? $request['request_payload'] : array();
        $cached = $this->aysa_competitor_identification_get_cached_payload($project_id);
        if (is_array($cached) && !empty($cached['data']) && is_array($cached['data'])) {
            $cached_request = isset($cached['request']) && is_array($cached['request']) ? $cached['request'] : array();
            $same_seeds = isset($cached_request['seed_keywords']) && is_array($cached_request['seed_keywords']) ? array_map('strtolower', array_values($cached_request['seed_keywords'])) : array();
            $current_seeds = isset($request_payload['seed_keywords']) && is_array($request_payload['seed_keywords']) ? array_map('strtolower', array_values($request_payload['seed_keywords'])) : array();
            sort($same_seeds);
            sort($current_seeds);
            if ($same_seeds === $current_seeds) {
                $data = $cached['data'];
                $data['cached'] = true;
                $data['cached_at'] = intval($cached['cached_at'] ?? 0);
                return $data;
            }
        }

        return null;
    }

    private function aysa_research_async_execute_competitor_identification($request) {
        if (!is_array($request)) {
            return new WP_Error('invalid_request', 'Invalid competitor identification request.');
        }

        $project_id = intval($request['project_id'] ?? 0);
        $seed_keywords = isset($request['seed_keywords']) && is_array($request['seed_keywords']) ? array_values($request['seed_keywords']) : array();
        $top_n = intval($request['top_n'] ?? 10);
        $request_payload = isset($request['request_payload']) && is_array($request['request_payload']) ? $request['request_payload'] : array();

        $cached = $this->aysa_research_async_get_competitor_identification_cached_result($request);
        if (is_array($cached)) {
            return $cached;
        }

        $last_error = '';
        $broker_result = $this->aysa_broker_request('POST', '/v1/competitors/identify/from-seeds', $request_payload, 180);
        if (is_wp_error($broker_result)) {
            $last_error = $broker_result->get_error_message();
        } else {
            $data = isset($broker_result['data']) && is_array($broker_result['data']) ? $broker_result['data'] : $broker_result;
            if (is_array($data) && !empty($data['error'])) {
                $last_error = (string) $data['error'];
            }
            if (is_array($data) && !empty($data['ok']) && isset($data['unique_competitors']) && is_array($data['unique_competitors'])) {
                $data = $this->aysa_keyword_v2_finalize_snapshot_contract($project_id, 'competitor_identification_v2', $data, 'unique_competitors');
                $this->aysa_competitor_identification_save_cached_payload($project_id, $request_payload, $data);
                $data['cached'] = false;
                $data['cached_at'] = time();
                $this->aysa_wallet_track_action_usage(
                    $project_id,
                    'aysa_competitor_identification_seed_competitors',
                    'competitor_identification_seed_competitors',
                    array(
                        'module' => 'competitor_identification',
                        'source' => 'broker',
                        'broker_path' => '/v1/competitors/identify/from-seeds',
                        'seed_keywords_count' => count($seed_keywords),
                        'top_n' => $top_n,
                    ),
                    35,
                    '/v1/competitors/identify/from-seeds'
                );
                return $data;
            }
        }

        $cached = $this->aysa_competitor_identification_get_cached_payload($project_id);
        if (is_array($cached) && !empty($cached['data']) && is_array($cached['data'])) {
            $data = $cached['data'];
            $data['cached'] = true;
            $data['cached_at'] = intval($cached['cached_at'] ?? 0);
            return $data;
        }

        $message = 'Could not identify competitors from seed keywords.';
        if ($last_error !== '') {
            $message .= ' ' . $last_error;
        }
        return new WP_Error('competitor_identification_failed', $message);
    }

    private function aysa_research_async_prepare_keyword_strategy_request($input) {
        if (!$this->check_authentication(false)) {
            return new WP_Error('auth_required', 'Authentication required');
        }
        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }
        $auth_manager = Aysa_Auth_Manager::get_instance();
        $auth_status = $auth_manager->is_authenticated();
        $user_id = intval($auth_status['aysa_user_id'] ?? 0);
        if ($user_id <= 0) {
            return new WP_Error('invalid_auth_context', 'Invalid authentication context');
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval($input['project_id'] ?? 0));
        if ($project_id <= 0) {
            return new WP_Error('invalid_project', 'Invalid project id');
        }

        $seed_data = $this->aysa_keyword_strategy_extract_seed_data($user_id);
        $seed_keywords = isset($seed_data['seed_keywords']) && is_array($seed_data['seed_keywords']) ? $seed_data['seed_keywords'] : array();
        $competitors = isset($seed_data['competitors']) && is_array($seed_data['competitors']) ? $seed_data['competitors'] : array();
        if (empty($seed_keywords)) {
            return new WP_Error('missing_seeds', 'No seed keywords found in Setup SEO Profile > Competition.');
        }

        $context = $this->aysa_keyword_strategy_get_context($user_id);
        $broker_payload = array(
            'project_id' => $project_id,
            'seed_keywords' => $seed_keywords,
            'competitors' => $competitors,
            'targeted_country' => (string) ($context['targeted_country'] ?? ''),
            'targeted_region' => (string) ($context['targeted_region'] ?? ''),
            'targeted_city' => (string) ($context['targeted_city'] ?? ''),
            'primary_language' => (string) ($context['primary_language'] ?? ''),
            'website_type' => (string) ($context['website_type'] ?? ''),
            'industry' => (string) ($context['industry'] ?? ''),
            'max_keywords' => 250,
        );

        return array(
            'task' => 'keyword_strategy_generate',
            'project_id' => $project_id,
            'user_id' => $user_id,
            'seed_keywords' => array_values($seed_keywords),
            'competitors' => array_values($competitors),
            'context' => $context,
            'broker_payload' => $broker_payload,
            'request_hash' => md5(wp_json_encode($broker_payload)),
        );
    }

    private function aysa_research_async_execute_keyword_strategy($request) {
        if (!is_array($request)) {
            return new WP_Error('invalid_request', 'Invalid keyword strategy request.');
        }

        $project_id = intval($request['project_id'] ?? 0);
        $user_id = intval($request['user_id'] ?? 0);
        $seed_keywords = isset($request['seed_keywords']) && is_array($request['seed_keywords']) ? $request['seed_keywords'] : array();
        $competitors = isset($request['competitors']) && is_array($request['competitors']) ? $request['competitors'] : array();
        $context = isset($request['context']) && is_array($request['context']) ? $request['context'] : array();
        $broker_payload = isset($request['broker_payload']) && is_array($request['broker_payload']) ? $request['broker_payload'] : array();

        $strategy = array();
        $used_remote = false;

        $remote_result = $this->aysa_broker_request('POST', '/v1/keywords/strategy/from-seeds', $broker_payload, 120);
        if (!is_wp_error($remote_result) && is_array($remote_result)) {
            $remote_data = isset($remote_result['data']) && is_array($remote_result['data']) ? $remote_result['data'] : $remote_result;
            $remote_items = isset($remote_data['items']) && is_array($remote_data['items']) ? $remote_data['items'] : array();
            if (!empty($remote_items)) {
                $strategy = array(
                    'project_id' => $project_id,
                    'user_id' => $user_id,
                    'generated_at' => current_time('mysql'),
                    'source' => 'broker',
                    'seed_keywords' => array_values($seed_keywords),
                    'competitors' => array_values($competitors),
                    'context' => $context,
                    'summary' => isset($remote_data['summary']) && is_array($remote_data['summary']) ? $remote_data['summary'] : array(),
                    'items' => array_values($remote_items),
                );
                $used_remote = true;
            }
        }

        if (empty($strategy)) {
            $strategy = $this->aysa_keyword_strategy_build_local_plan($project_id, $user_id, $seed_keywords, $competitors, $context);
        }

        $option_key = $this->aysa_keyword_strategy_option_key($project_id, $user_id);
        update_option($option_key, $strategy, false);
        $this->aysa_adjustments_add_pending_approval(
            $project_id,
            'keyword_strategy_review',
            'Keyword strategy is ready for approval',
            'I finished the keyword strategy. I prepared the initial mapping, the opportunities, and the recommended directions. I need your approval before I continue into tracking, content, and execution.',
            'open_keyword_discovery'
        );

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_keyword_discovery_generate_strategy',
            'keyword_discovery_generate_strategy',
            array(
                'module' => 'keyword_discovery',
                'source' => $used_remote ? 'broker' : 'local_fallback',
                'seed_keywords_count' => count($seed_keywords),
                'competitors_count' => count($competitors),
            ),
            25,
            '/v1/keywords/strategy/generate'
        );

        return array(
            'project_id' => $project_id,
            'user_id' => $user_id,
            'strategy' => $strategy,
            'source' => $used_remote ? 'broker' : 'local_fallback',
        );
    }

    private function aysa_research_async_schedule_request($task, $request) {
        $task = sanitize_key((string) $task);
        if (!in_array($task, $this->aysa_research_async_supported_tasks(), true) || !is_array($request)) {
            return new WP_Error('unsupported_task', 'Unsupported research task.');
        }

        $project_id = intval($request['project_id'] ?? 0);
        $request_hash = (string) ($request['request_hash'] ?? '');
        if ($project_id <= 0 || $request_hash === '') {
            return new WP_Error('invalid_research_payload', 'Invalid research task payload.');
        }

        if ($task === 'competitor_gap') {
            $cached = $this->aysa_research_async_get_competitor_gap_cached_result($request);
            if (is_array($cached)) {
                return array(
                    'status' => 'completed',
                    'worker_ref' => '',
                    'worker_status' => 'completed',
                    'result_summary' => array(
                        'cached' => true,
                        'count' => count((array) ($cached['rankedKeywords'] ?? array())),
                    ),
                    'result' => $cached,
                    'started_at' => time(),
                );
            }
        } elseif ($task === 'competitor_identification') {
            $cached = $this->aysa_research_async_get_competitor_identification_cached_result($request);
            if (is_array($cached)) {
                return array(
                    'status' => 'completed',
                    'worker_ref' => '',
                    'worker_status' => 'completed',
                    'result_summary' => array(
                        'cached' => true,
                        'count' => count((array) ($cached['unique_competitors'] ?? array())),
                    ),
                    'result' => $cached,
                    'started_at' => time(),
                );
            }
        }

        $existing = $this->aysa_research_async_get_status($project_id, $task);
        if (
            is_array($existing) &&
            !empty($existing['run_id']) &&
            in_array((string) ($existing['status'] ?? ''), array('queued', 'running'), true) &&
            (string) ($existing['request_hash'] ?? '') === $request_hash
        ) {
            $run_id = (string) ($existing['run_id'] ?? '');
            return array(
                'status' => 'running',
                'worker_ref' => $this->aysa_project_jobs_make_worker_ref('research_async', $task, $run_id),
                'worker_status' => (string) ($existing['status'] ?? 'queued'),
                'result_summary' => array('deduped_worker' => true),
                'started_at' => intval($existing['started_at'] ?? time()),
            );
        }

        $run_id = uniqid('research_', true);
        $status = array(
            'task' => $task,
            'project_id' => $project_id,
            'run_id' => $run_id,
            'request_hash' => $request_hash,
            'status' => 'queued',
            'result' => null,
            'error' => '',
            'started_at' => time(),
            'updated_at' => time(),
            'finished_at' => 0,
        );
        $this->aysa_research_async_save_status($project_id, $task, $status);
        $this->aysa_research_async_schedule_event(array(
            'task' => $task,
            'project_id' => $project_id,
            'run_id' => $run_id,
            'request_hash' => $request_hash,
            'request' => $request,
        ), 1);

        return array(
            'status' => 'running',
            'worker_ref' => $this->aysa_project_jobs_make_worker_ref('research_async', $task, $run_id),
            'worker_status' => 'queued',
            'result_summary' => null,
            'started_at' => time(),
        );
    }

    private function aysa_monitoring_keywords_async_schedule_request($request) {
        if (!is_array($request)) {
            return new WP_Error('invalid_monitoring_keywords_request', 'Invalid monitoring keywords async request.');
        }

        $project_id = intval($request['project_id'] ?? 0);
        $request_hash = (string) ($request['request_hash'] ?? '');
        if ($project_id <= 0 || $request_hash === '') {
            return new WP_Error('invalid_monitoring_keywords_request', 'Invalid monitoring keywords async request.');
        }

        $existing = $this->aysa_monitoring_keywords_async_get_status($project_id);
        if (
            is_array($existing) &&
            !empty($existing['run_id']) &&
            in_array((string) ($existing['status'] ?? ''), array('queued', 'running'), true) &&
            (string) ($existing['request_hash'] ?? '') === $request_hash
        ) {
            return array(
                'status' => (string) ($existing['status'] ?? 'running'),
                'worker_ref' => $this->aysa_project_jobs_make_worker_ref('monitoring_keywords_async', 'keywords_overview_refresh', (string) ($existing['run_id'] ?? '')),
                'worker_status' => (string) ($existing['status'] ?? ''),
                'result_summary' => array('deduped_worker' => true),
                'started_at' => intval($existing['started_at'] ?? time()),
            );
        }

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_monitoring_keywords_overview',
            'monitoring_keywords_overview',
            array(
                'module' => 'monitoring_analytics',
                'source' => 'manual_refresh_async',
            ),
            60,
            '/v1/monitoring/keywords/overview'
        );

        $run_id = uniqid('mon_kw_', true);
        $status = array(
            'project_id' => $project_id,
            'domain' => (string) ($request['domain'] ?? ''),
            'run_id' => $run_id,
            'request_hash' => $request_hash,
            'status' => 'queued',
            'result' => null,
            'error' => '',
            'started_at' => time(),
            'updated_at' => time(),
            'finished_at' => 0,
            '_request' => $request,
        );
        $this->aysa_monitoring_keywords_async_save_status($project_id, $status);

        wp_schedule_single_event(time() + 1, 'aysa_monitoring_keywords_async_event', array(array(
            'project_id' => $project_id,
            'run_id' => $run_id,
            'request_hash' => $request_hash,
            'request' => $request,
        )));
        if (function_exists('spawn_cron')) {
            spawn_cron();
        }

        return array(
            'status' => 'running',
            'worker_ref' => $this->aysa_project_jobs_make_worker_ref('monitoring_keywords_async', 'keywords_overview_refresh', $run_id),
            'worker_status' => 'queued',
            'result_summary' => null,
            'started_at' => time(),
        );
    }

    public function handle_research_async_start() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $task = sanitize_key((string) wp_unslash($_POST['task'] ?? ''));
        if (!in_array($task, $this->aysa_research_async_supported_tasks(), true)) {
            wp_send_json_error('Unsupported research task.');
            return;
        }

        switch ($task) {
            case 'competitor_gap':
                $request = $this->aysa_research_async_prepare_competitor_gap_request($_POST);
                break;
            case 'competitor_identification':
                $request = $this->aysa_research_async_prepare_competitor_identification_request($_POST);
                break;
            case 'keyword_strategy_generate':
                $request = $this->aysa_research_async_prepare_keyword_strategy_request($_POST);
                break;
            default:
                $request = new WP_Error('unsupported_task', 'Unsupported research task.');
        }

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

        $project_id = intval($request['project_id'] ?? 0);
        $request_hash = (string) ($request['request_hash'] ?? '');
        if ($project_id <= 0 || $request_hash === '') {
            wp_send_json_error('Invalid research task payload.');
            return;
        }

        $job = $this->aysa_project_jobs_enqueue(
            $project_id,
            'research',
            $task,
            array(
                'task' => $task,
                'request' => $request,
            ),
            array(
                'dedupe_key' => $request_hash,
                'input_summary' => array(
                    'task' => $task,
                    'request_hash' => $request_hash,
                ),
            )
        );
        if (is_wp_error($job)) {
            wp_send_json_error($job->get_error_message());
            return;
        }

        $job_row = isset($job['job']) && is_array($job['job']) ? $job['job'] : array();
        wp_send_json_success(array(
            'task' => $task,
            'project_id' => $project_id,
            'run_id' => (string) ($job_row['job_id'] ?? ''),
            'status' => (string) ($job_row['status'] ?? 'queued'),
            'result' => $job_row['result_payload'] ?? null,
            'error' => (string) ($job_row['error_summary'] ?? ''),
            'worker_ref' => (string) ($job_row['worker_ref'] ?? ''),
            'worker_status' => (string) ($job_row['worker_status'] ?? ''),
            'blocked_by' => array_values((array) ($job_row['blocked_by'] ?? array())),
            'project_job_rack' => isset($job['rack']) && is_array($job['rack']) ? $job['rack'] : array(),
        ));
    }

    public function handle_research_async_status() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $task = sanitize_key((string) wp_unslash($_POST['task'] ?? ''));
        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0 || !in_array($task, $this->aysa_research_async_supported_tasks(), true)) {
            wp_send_json_error('Invalid research task status request.');
            return;
        }

        $status = $this->aysa_research_async_get_status($project_id, $task);
        if (empty($status)) {
            $job = $this->aysa_project_jobs_find_latest_job($project_id, 'research', $task, array('queued', 'running', 'completed', 'failed'));
            if (is_array($job)) {
                wp_send_json_success(array(
                    'task' => $task,
                    'project_id' => $project_id,
                    'run_id' => (string) ($job['job_id'] ?? ''),
                    'status' => (string) ($job['status'] ?? 'idle'),
                    'result' => $job['result_payload'] ?? null,
                    'error' => (string) ($job['error_summary'] ?? ''),
                    'worker_ref' => (string) ($job['worker_ref'] ?? ''),
                    'project_job_rack' => $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state($project_id)),
                ));
                return;
            }
            wp_send_json_success(array(
                'task' => $task,
                'project_id' => $project_id,
                'status' => 'idle',
                'result' => null,
                'error' => '',
            ));
            return;
        }
        $status['project_job_rack'] = $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state($project_id));
        wp_send_json_success($status);
    }

    public function run_research_async_event($payload) {
        if (!is_array($payload)) {
            return;
        }

        $task = sanitize_key((string) ($payload['task'] ?? ''));
        $project_id = $this->aysa_resolve_installed_project_id(intval($payload['project_id'] ?? 0));
        $run_id = (string) ($payload['run_id'] ?? '');
        $request_hash = (string) ($payload['request_hash'] ?? '');
        $request = isset($payload['request']) && is_array($payload['request']) ? $payload['request'] : array();
        if (
            $project_id <= 0 ||
            intval($request['project_id'] ?? 0) !== $project_id ||
            $run_id === '' ||
            $request_hash === '' ||
            !in_array($task, $this->aysa_research_async_supported_tasks(), true)
        ) {
            return;
        }

        $status = $this->aysa_research_async_get_status($project_id, $task);
        if (
            empty($status) ||
            (string) ($status['run_id'] ?? '') !== $run_id ||
            (string) ($status['request_hash'] ?? '') !== $request_hash
        ) {
            return;
        }

        $status['status'] = 'running';
        $status['updated_at'] = time();
        $status['error'] = '';
        $this->aysa_research_async_save_status($project_id, $task, $status);

        switch ($task) {
            case 'competitor_gap':
                $result = $this->aysa_research_async_execute_competitor_gap($request);
                break;
            case 'competitor_identification':
                $result = $this->aysa_research_async_execute_competitor_identification($request);
                break;
            case 'keyword_strategy_generate':
                $result = $this->aysa_research_async_execute_keyword_strategy($request);
                break;
            default:
                $result = new WP_Error('unsupported_task', 'Unsupported research task.');
        }

        $status['updated_at'] = time();
        $status['finished_at'] = time();
        if (is_wp_error($result)) {
            $status['status'] = 'failed';
            $status['result'] = null;
            $status['error'] = $result->get_error_message();
        } else {
            $status['status'] = 'completed';
            $status['result'] = $result;
            $status['error'] = '';
        }
        $this->aysa_research_async_save_status($project_id, $task, $status);
        $this->aysa_project_jobs_finalize(
            $project_id,
            $this->aysa_project_jobs_make_worker_ref('research_async', $task, $run_id),
            (string) ($status['status'] ?? 'failed'),
            !is_wp_error($result) && is_array($result) ? array(
                'task' => $task,
                'status' => (string) ($status['status'] ?? ''),
            ) : null,
            is_wp_error($result) ? $result->get_error_message() : ''
        );
    }

    private function aysa_offpage_async_supported_tasks() {
        return array(
            'publisher_draft_create',
            'publisher_draft_update',
            'publisher_draft_approve',
            'exchange_request_create',
            'exchange_activate_link',
            'exchange_simulate_accept',
            'exchange_request_respond',
            'exchange_refresh_verification',
            'exchange_change_state',
            'exchange_submit_publication',
        );
    }

    private function aysa_offpage_async_status_key($project_id, $task) {
        return 'aysa_offpage_async_' . sanitize_key((string) $task) . '_' . intval($project_id);
    }

    private function aysa_offpage_async_get_status($project_id, $task) {
        $raw = get_option($this->aysa_offpage_async_status_key($project_id, $task), array());
        return is_array($raw) ? $raw : array();
    }

    private function aysa_offpage_async_save_status($project_id, $task, $status) {
        update_option($this->aysa_offpage_async_status_key($project_id, $task), is_array($status) ? $status : array(), false);
    }

    private function aysa_offpage_async_schedule_event($payload, $delay_seconds = 1) {
        if (!is_array($payload)) {
            return;
        }
        $timestamp = time() + max(1, intval($delay_seconds));
        wp_schedule_single_event($timestamp, 'aysa_offpage_async_event', array($payload));
        if (function_exists('spawn_cron')) {
            spawn_cron();
        }
    }

    private function aysa_offpage_async_prepare_request($task, $input) {
        $project_id = $this->aysa_resolve_project_id_with_local_fallback(intval($input['project_id'] ?? get_option('aysa_project_id', 0)));

        switch ($task) {
            case 'publisher_draft_create':
                $order_ref = sanitize_text_field((string) ($input['order_ref'] ?? ''));
                if ($project_id <= 0 || $order_ref === '') {
                    return new WP_Error('invalid_request', 'Invalid project_id or order_ref');
                }
                return array(
                    'task' => $task,
                    'project_id' => $project_id,
                    'order_ref' => $order_ref,
                    'request_hash' => md5($project_id . '|' . $order_ref),
                );

            case 'publisher_draft_update':
                $draft_id = intval($input['draft_id'] ?? 0);
                if ($draft_id <= 0) {
                    return new WP_Error('invalid_request', 'Invalid draft_id');
                }
                return array(
                    'task' => $task,
                    'project_id' => $project_id,
                    'draft_id' => $draft_id,
                    'title' => sanitize_text_field((string) ($input['title'] ?? '')),
                    'excerpt' => sanitize_textarea_field((string) ($input['excerpt'] ?? '')),
                    'body_html' => wp_kses_post((string) ($input['body_html'] ?? '')),
                    'status_value' => sanitize_key((string) ($input['status'] ?? 'draft')),
                    'request_hash' => md5($project_id . '|' . $draft_id . '|' . sanitize_text_field((string) ($input['title'] ?? ''))),
                );

            case 'publisher_draft_approve':
                $draft_id = intval($input['draft_id'] ?? 0);
                if ($draft_id <= 0) {
                    return new WP_Error('invalid_request', 'Invalid draft_id');
                }
                return array(
                    'task' => $task,
                    'project_id' => $project_id,
                    'draft_id' => $draft_id,
                    'request_hash' => md5($project_id . '|approve|' . $draft_id),
                );

            case 'exchange_request_create':
                $portal_json = (string) ($input['counterparty_portal_json'] ?? '');
                $portal = json_decode(wp_unslash($portal_json), true);
                if ($project_id <= 0) {
                    return new WP_Error('invalid_request', 'Invalid project ID.');
                }
                if (!is_array($portal) || empty($portal)) {
                    return new WP_Error('invalid_request', 'Counterparty portal is required.');
                }
                return array(
                    'task' => $task,
                    'project_id' => $project_id,
                    'portal' => $portal,
                    'requester_target_url' => esc_url_raw(trim((string) ($input['requester_target_url'] ?? ''))),
                    'requester_anchor_text' => sanitize_text_field((string) ($input['requester_anchor_text'] ?? '')),
                    'counterparty_target_url' => esc_url_raw(trim((string) ($input['counterparty_target_url'] ?? ''))),
                    'counterparty_anchor_text' => sanitize_text_field((string) ($input['counterparty_anchor_text'] ?? '')),
                    'requester_notes' => sanitize_textarea_field((string) ($input['requester_notes'] ?? '')),
                    'request_hash' => md5($project_id . '|' . wp_json_encode($portal) . '|' . (string) ($input['requester_target_url'] ?? '') . '|' . (string) ($input['counterparty_target_url'] ?? '')),
                );

            case 'exchange_activate_link':
                $source_post_id = intval($input['source_post_id'] ?? 0);
                $exchange_ref = sanitize_text_field((string) ($input['exchange_ref'] ?? ''));
                if ($project_id <= 0) {
                    return new WP_Error('invalid_request', 'Invalid project ID.');
                }
                return array(
                    'task' => $task,
                    'project_id' => $project_id,
                    'exchange_ref' => $exchange_ref,
                    'request_id' => intval($input['request_id'] ?? 0),
                    'source_post_id' => $source_post_id,
                    'notes' => sanitize_textarea_field((string) ($input['notes'] ?? '')),
                    'request_hash' => md5($project_id . '|activate|' . $exchange_ref . '|' . $source_post_id),
                );

            case 'exchange_simulate_accept':
                if ($project_id <= 0) {
                    return new WP_Error('invalid_request', 'Invalid project ID.');
                }
                return array(
                    'task' => $task,
                    'project_id' => $project_id,
                    'request_id' => intval($input['request_id'] ?? 0),
                    'exchange_ref' => sanitize_text_field((string) ($input['exchange_ref'] ?? '')),
                    'source' => sanitize_text_field((string) ($input['source'] ?? 'agent8_v3_legacy_admin')),
                    'request_hash' => md5($project_id . '|simulate_accept|' . (string) ($input['exchange_ref'] ?? '')),
                );

            case 'exchange_request_respond':
                if ($project_id <= 0) {
                    return new WP_Error('invalid_request', 'Invalid project ID.');
                }
                return array(
                    'task' => $task,
                    'project_id' => $project_id,
                    'request_id' => intval($input['request_id'] ?? 0),
                    'exchange_ref' => sanitize_text_field((string) ($input['exchange_ref'] ?? '')),
                    'decision' => sanitize_text_field((string) ($input['decision'] ?? 'accept')),
                    'counterparty_target_url' => esc_url_raw(trim((string) ($input['counterparty_target_url'] ?? ''))),
                    'counterparty_anchor_text' => sanitize_text_field((string) ($input['counterparty_anchor_text'] ?? '')),
                    'counterparty_notes' => sanitize_textarea_field((string) ($input['counterparty_notes'] ?? '')),
                    'request_hash' => md5($project_id . '|respond|' . (string) ($input['exchange_ref'] ?? '') . '|' . (string) ($input['decision'] ?? 'accept')),
                );

            case 'exchange_refresh_verification':
                if ($project_id <= 0) {
                    return new WP_Error('invalid_request', 'Invalid project ID.');
                }
                return array(
                    'task' => $task,
                    'project_id' => $project_id,
                    'request_id' => intval($input['request_id'] ?? 0),
                    'exchange_ref' => sanitize_text_field((string) ($input['exchange_ref'] ?? '')),
                    'request_hash' => md5($project_id . '|verify|' . (string) ($input['exchange_ref'] ?? '')),
                );

            case 'exchange_change_state':
                if ($project_id <= 0) {
                    return new WP_Error('invalid_request', 'Invalid project ID.');
                }
                return array(
                    'task' => $task,
                    'project_id' => $project_id,
                    'request_id' => intval($input['request_id'] ?? 0),
                    'exchange_ref' => sanitize_text_field((string) ($input['exchange_ref'] ?? '')),
                    'state_action' => sanitize_text_field((string) ($input['state_action'] ?? $input['action_name'] ?? '')),
                    'reason' => sanitize_textarea_field((string) ($input['reason'] ?? '')),
                    'request_hash' => md5($project_id . '|state|' . (string) ($input['exchange_ref'] ?? '') . '|' . (string) ($input['state_action'] ?? $input['action_name'] ?? '')),
                );

            case 'exchange_submit_publication':
                if ($project_id <= 0) {
                    return new WP_Error('invalid_request', 'Invalid project ID.');
                }
                return array(
                    'task' => $task,
                    'project_id' => $project_id,
                    'request_id' => intval($input['request_id'] ?? 0),
                    'exchange_ref' => sanitize_text_field((string) ($input['exchange_ref'] ?? '')),
                    'published_url' => esc_url_raw(trim((string) ($input['published_url'] ?? ''))),
                    'target_url' => esc_url_raw(trim((string) ($input['target_url'] ?? ''))),
                    'anchor_text' => sanitize_text_field((string) ($input['anchor_text'] ?? '')),
                    'notes' => sanitize_textarea_field((string) ($input['notes'] ?? '')),
                    'request_hash' => md5($project_id . '|publication|' . (string) ($input['exchange_ref'] ?? '') . '|' . (string) ($input['published_url'] ?? '')),
                );
        }

        return new WP_Error('unsupported_task', 'Unsupported off-page task.');
    }

    private function aysa_offpage_async_execute($request) {
        if (!is_array($request)) {
            return new WP_Error('invalid_request', 'Invalid off-page request.');
        }

        $task = sanitize_key((string) ($request['task'] ?? ''));
        $project_id = intval($request['project_id'] ?? 0);

        switch ($task) {
            case 'publisher_draft_create':
                $order_ref = sanitize_text_field((string) ($request['order_ref'] ?? ''));
                $orders = $this->aysa_get_adverlink_orders($project_id);
                $matched_order = null;
                foreach ($orders as $order) {
                    if (!is_array($order)) {
                        continue;
                    }
                    if (sanitize_text_field((string) ($order['order_ref'] ?? '')) === $order_ref) {
                        $matched_order = $order;
                        break;
                    }
                }
                if (!is_array($matched_order)) {
                    return new WP_Error('order_not_found', 'Order not found.');
                }
                $result = $this->aysa_create_publisher_article_draft_for_order($project_id, $matched_order);
                if (is_wp_error($result)) {
                    return $result;
                }
                $this->aysa_wallet_track_action_usage(
                    $project_id,
                    'aysa_adverlink_create_publisher_article_draft',
                    'offpage_create_publisher_article_draft_' . md5($order_ref),
                    array('module' => 'offpage', 'order_ref' => $order_ref),
                    12,
                    'wp_ajax/aysa_adverlink_create_publisher_article_draft'
                );
                return $result;

            case 'publisher_draft_update':
                $draft_id = intval($request['draft_id'] ?? 0);
                $status_value = sanitize_key((string) ($request['status_value'] ?? 'draft'));
                $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/publisher-articles/update', array(
                    'draft_id' => $draft_id,
                    'title' => sanitize_text_field((string) ($request['title'] ?? '')),
                    'excerpt' => sanitize_textarea_field((string) ($request['excerpt'] ?? '')),
                    'body_html' => wp_kses_post((string) ($request['body_html'] ?? '')),
                    'status' => $status_value !== '' ? $status_value : 'edited',
                ), 60);
                if (is_wp_error($broker_result)) {
                    return new WP_Error('broker_error', $this->aysa_humanize_broker_error($broker_result, 'Could not save draft.'));
                }
                $draft = isset($broker_result['draft']) && is_array($broker_result['draft']) ? $broker_result['draft'] : array();
                $this->aysa_wallet_track_action_usage(
                    $project_id,
                    'aysa_adverlink_update_publisher_article_draft',
                    'offpage_update_publisher_article_draft_' . $draft_id,
                    array('module' => 'offpage', 'draft_id' => $draft_id, 'status' => $status_value !== '' ? $status_value : 'edited'),
                    8,
                    'wp_ajax/aysa_adverlink_update_publisher_article_draft'
                );
                return array('draft' => $draft);

            case 'publisher_draft_approve':
                $draft_id = intval($request['draft_id'] ?? 0);
                if ($this->aysa_agent8_is_publisher_draft_approved($project_id, $draft_id)) {
                    $this->aysa_agent8_mark_publisher_draft_approved($project_id, $draft_id);
                    return array('draft' => array('id' => $draft_id, 'status' => 'approved'), 'already_approved' => true);
                }
                $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/publisher-articles/approve', array(
                    'draft_id' => $draft_id,
                ), 60);
                if (is_wp_error($broker_result)) {
                    return new WP_Error('broker_error', $this->aysa_humanize_broker_error($broker_result, 'Could not approve draft.'));
                }
                $draft = isset($broker_result['draft']) && is_array($broker_result['draft']) ? $broker_result['draft'] : array();
                $this->aysa_agent8_mark_publisher_draft_approved($project_id, $draft_id, $draft);
                $reference = 'offpage_approve_publisher_article_draft_' . $draft_id;
                if (!$this->aysa_wallet_usage_reference_exists($project_id, $reference)) {
                    $this->aysa_wallet_track_action_usage(
                        $project_id,
                        'aysa_adverlink_approve_publisher_article_draft',
                        $reference,
                        array('module' => 'offpage', 'draft_id' => $draft_id),
                        8,
                        'wp_ajax/aysa_adverlink_approve_publisher_article_draft'
                    );
                }
                return array('draft' => $draft);

            case 'exchange_request_create':
                $requester = $this->aysa_get_link_exchange_profile($project_id);
                $payload = array(
                    'project_id' => $project_id,
                    'requester_site_url' => $requester['site_url'],
                    'requester_site_domain' => $requester['site_domain'],
                    'requester_website_type' => $requester['website_type'],
                    'requester_industry' => $requester['industry'],
                    'requester_language' => $requester['language'],
                    'requester_country' => $requester['country'],
                    'requester_topics' => array_values($requester['topics']),
                    'requester_authority_value' => intval($requester['authority_value'] ?? 0),
                    'counterparty_portal' => isset($request['portal']) && is_array($request['portal']) ? $request['portal'] : array(),
                    'requester_target_url' => esc_url_raw((string) ($request['requester_target_url'] ?? '')),
                    'requester_anchor_text' => sanitize_text_field((string) ($request['requester_anchor_text'] ?? '')),
                    'counterparty_target_url' => esc_url_raw((string) ($request['counterparty_target_url'] ?? '')),
                    'counterparty_anchor_text' => sanitize_text_field((string) ($request['counterparty_anchor_text'] ?? '')),
                    'requester_notes' => sanitize_textarea_field((string) ($request['requester_notes'] ?? '')),
                );
                $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/create', $payload, 45);
                if (is_wp_error($broker_result)) {
                    return new WP_Error('broker_error', $this->aysa_humanize_broker_error($broker_result, 'Could not create exchange request.'));
                }
                $this->aysa_wallet_track_action_usage(
                    $project_id,
                    'aysa_adverlink_exchange_create_request',
                    'offpage_exchange_create_request_' . md5((string) ($payload['requester_target_url'] ?? '') . '|' . (string) ($payload['counterparty_target_url'] ?? '')),
                    array('module' => 'offpage', 'source' => 'aysa_exchange_network'),
                    12,
                    'wp_ajax/aysa_adverlink_exchange_create_request'
                );
                return array(
                    'request' => isset($broker_result['request']) && is_array($broker_result['request']) ? $broker_result['request'] : array(),
                    'deduplicated' => !empty($broker_result['deduplicated']),
                );

            case 'exchange_activate_link':
                $requester = $this->aysa_get_link_exchange_profile($project_id);
                $request_payload = array(
                    'project_id' => $project_id,
                    'current_site_domain' => $requester['site_domain'],
                    'request_id' => intval($request['request_id'] ?? 0),
                    'exchange_ref' => sanitize_text_field((string) ($request['exchange_ref'] ?? '')),
                );
                $request_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/get', $request_payload, 30);
                if (is_wp_error($request_result)) {
                    return new WP_Error('broker_error', $this->aysa_humanize_broker_error($request_result, 'Could not load exchange request before activation.'));
                }
                $exchange_request = isset($request_result['request']) && is_array($request_result['request']) ? $request_result['request'] : array();
                if (empty($exchange_request)) {
                    return new WP_Error('request_not_found', 'Exchange request not found.');
                }
                $source_post_id = intval($request['source_post_id'] ?? 0);
                $role = (string) ($exchange_request['viewer_role'] ?? '');
                $target_url = $role === 'requester'
                    ? esc_url_raw((string) ($exchange_request['counterparty_target_url'] ?? ''))
                    : esc_url_raw((string) ($exchange_request['requester_target_url'] ?? ''));
                $anchor_text = $role === 'requester'
                    ? sanitize_text_field((string) ($exchange_request['counterparty_anchor_text'] ?? ''))
                    : sanitize_text_field((string) ($exchange_request['requester_anchor_text'] ?? ''));
                if ($target_url === '' || $anchor_text === '') {
                    return new WP_Error('missing_target', 'Target URL and anchor are missing for activation.');
                }
                if ($source_post_id <= 0) {
                    $source_post_id = $this->aysa_exchange_find_auto_source_post_id($target_url, $anchor_text);
                }
                if ($source_post_id <= 0) {
                    return new WP_Error('exchange_source_required', 'No safe published source content was found for automatic partner box insertion.');
                }
                $source_post = get_post($source_post_id);
                $semantic = $this->aysa_exchange_score_source_candidate($source_post, $target_url, $anchor_text);
                $activation = $this->aysa_exchange_insert_link_into_post($source_post_id, $target_url, $anchor_text, (string) ($exchange_request['exchange_ref'] ?? ''));
                if (is_wp_error($activation)) {
                    return $activation;
                }
                $payload = array(
                    'project_id' => $project_id,
                    'current_site_domain' => $requester['site_domain'],
                    'request_id' => intval($exchange_request['id'] ?? 0),
                    'exchange_ref' => sanitize_text_field((string) ($exchange_request['exchange_ref'] ?? '')),
                    'source_post_id' => $source_post_id,
                    'source_url' => esc_url_raw((string) ($activation['source_url'] ?? '')),
                    'published_url' => esc_url_raw((string) ($activation['source_url'] ?? '')),
                    'target_url' => $target_url,
                    'anchor_text' => $anchor_text,
                    'notes' => sanitize_textarea_field((string) ($request['notes'] ?? '')),
                );
                $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/activate', $payload, 45);
                if (is_wp_error($broker_result)) {
                    return new WP_Error('broker_error', $this->aysa_humanize_broker_error($broker_result, 'Could not activate reciprocal link.'));
                }
                if (empty($request['suppress_billing'])) {
                    $this->aysa_wallet_track_action_usage(
                        $project_id,
                        'aysa_adverlink_exchange_activate_link',
                        'offpage_exchange_activate_link_' . md5((string) ($exchange_request['exchange_ref'] ?? '') . '|' . (string) $source_post_id),
                        array(
                            'module' => 'offpage',
                            'exchange_ref' => (string) ($exchange_request['exchange_ref'] ?? ''),
                            'source_post_id' => $source_post_id,
                        ),
                        12,
                        'wp_ajax/aysa_adverlink_exchange_activate_link'
                    );
                }
                return array(
                    'request' => isset($broker_result['request']) && is_array($broker_result['request']) ? $broker_result['request'] : array(),
                    'activation' => $activation,
                    'semantic' => $semantic,
                    'placement_mode' => !empty($semantic['eligible']) ? 'semantic_match' : 'partner_box_fallback',
                );

            case 'exchange_simulate_accept':
                $requester = $this->aysa_get_link_exchange_profile($project_id);
                $payload = array(
                    'project_id' => $project_id,
                    'current_site_domain' => $requester['site_domain'],
                    'request_id' => intval($request['request_id'] ?? 0),
                    'exchange_ref' => sanitize_text_field((string) ($request['exchange_ref'] ?? '')),
                    'source' => sanitize_text_field((string) ($request['source'] ?? 'agent8_v3_legacy_admin')),
                );
                $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/simulate-accept', $payload, 30);
                if (is_wp_error($broker_result)) {
                    return new WP_Error('broker_error', $this->aysa_humanize_broker_error($broker_result, 'Could not simulate legacy exchange acceptance.'));
                }
                return array(
                    'request' => isset($broker_result['request']) && is_array($broker_result['request']) ? $broker_result['request'] : array(),
                    'simulated' => !empty($broker_result['simulated']),
                );

            case 'exchange_request_respond':
                $requester = $this->aysa_get_link_exchange_profile($project_id);
                $payload = array(
                    'project_id' => $project_id,
                    'current_site_domain' => $requester['site_domain'],
                    'request_id' => intval($request['request_id'] ?? 0),
                    'exchange_ref' => sanitize_text_field((string) ($request['exchange_ref'] ?? '')),
                    'decision' => sanitize_text_field((string) ($request['decision'] ?? 'accept')),
                    'counterparty_target_url' => esc_url_raw(trim((string) ($request['counterparty_target_url'] ?? ''))),
                    'counterparty_anchor_text' => sanitize_text_field((string) ($request['counterparty_anchor_text'] ?? '')),
                    'counterparty_notes' => sanitize_textarea_field((string) ($request['counterparty_notes'] ?? '')),
                );
                $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/respond', $payload, 30);
                if (is_wp_error($broker_result)) {
                    return new WP_Error('broker_error', $this->aysa_humanize_broker_error($broker_result, 'Could not update exchange request.'));
                }
                if (empty($request['suppress_billing'])) {
                    $this->aysa_wallet_track_action_usage(
                        $project_id,
                        'aysa_adverlink_exchange_respond_request',
                        'offpage_exchange_respond_request_' . md5((string) ($payload['exchange_ref'] ?? '') . '|' . (string) ($payload['decision'] ?? '')),
                        array('module' => 'offpage', 'decision' => (string) ($payload['decision'] ?? '')),
                        8,
                        'wp_ajax/aysa_adverlink_exchange_respond_request'
                    );
                }
                return array(
                    'request' => isset($broker_result['request']) && is_array($broker_result['request']) ? $broker_result['request'] : array(),
                );

            case 'exchange_refresh_verification':
                $requester = $this->aysa_get_link_exchange_profile($project_id);
                $payload = array(
                    'project_id' => $project_id,
                    'current_site_domain' => $requester['site_domain'],
                    'request_id' => intval($request['request_id'] ?? 0),
                    'exchange_ref' => sanitize_text_field((string) ($request['exchange_ref'] ?? '')),
                );
                $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/verify', $payload, 45);
                if (is_wp_error($broker_result)) {
                    return new WP_Error('broker_error', $this->aysa_humanize_broker_error($broker_result, 'Could not refresh exchange verification.'));
                }
                if (empty($request['suppress_billing'])) {
                    $this->aysa_wallet_track_action_usage(
                        $project_id,
                        'aysa_adverlink_exchange_refresh_verification',
                        'offpage_exchange_refresh_verification_' . md5((string) ($payload['exchange_ref'] ?? '')),
                        array('module' => 'offpage', 'exchange_ref' => (string) ($payload['exchange_ref'] ?? '')),
                        6,
                        'wp_ajax/aysa_adverlink_exchange_refresh_verification'
                    );
                }
                return array(
                    'request' => isset($broker_result['request']) && is_array($broker_result['request']) ? $broker_result['request'] : array(),
                );

            case 'exchange_change_state':
                $requester = $this->aysa_get_link_exchange_profile($project_id);
                $payload = array(
                    'project_id' => $project_id,
                    'current_site_domain' => $requester['site_domain'],
                    'request_id' => intval($request['request_id'] ?? 0),
                    'exchange_ref' => sanitize_text_field((string) ($request['exchange_ref'] ?? '')),
                    'action' => sanitize_text_field((string) ($request['state_action'] ?? '')),
                    'reason' => sanitize_textarea_field((string) ($request['reason'] ?? '')),
                );
                $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/state', $payload, 30);
                if (is_wp_error($broker_result)) {
                    return new WP_Error('broker_error', $this->aysa_humanize_broker_error($broker_result, 'Could not update exchange request state.'));
                }
                if (empty($request['suppress_billing'])) {
                    $this->aysa_wallet_track_action_usage(
                        $project_id,
                        'aysa_adverlink_exchange_change_state',
                        'offpage_exchange_change_state_' . md5((string) ($payload['exchange_ref'] ?? '') . '|' . (string) ($payload['action'] ?? '')),
                        array(
                            'module' => 'offpage',
                            'action' => (string) ($payload['action'] ?? ''),
                            'exchange_ref' => (string) ($payload['exchange_ref'] ?? ''),
                        ),
                        6,
                        'wp_ajax/aysa_adverlink_exchange_change_state'
                    );
                }
                return array(
                    'request' => isset($broker_result['request']) && is_array($broker_result['request']) ? $broker_result['request'] : array(),
                );

            case 'exchange_submit_publication':
                $requester = $this->aysa_get_link_exchange_profile($project_id);
                $payload = array(
                    'project_id' => $project_id,
                    'current_site_domain' => $requester['site_domain'],
                    'request_id' => intval($request['request_id'] ?? 0),
                    'exchange_ref' => sanitize_text_field((string) ($request['exchange_ref'] ?? '')),
                    'published_url' => esc_url_raw(trim((string) ($request['published_url'] ?? ''))),
                    'target_url' => esc_url_raw(trim((string) ($request['target_url'] ?? ''))),
                    'anchor_text' => sanitize_text_field((string) ($request['anchor_text'] ?? '')),
                    'notes' => sanitize_textarea_field((string) ($request['notes'] ?? '')),
                );
                $broker_result = $this->aysa_broker_request('POST', '/adverlink/v1/exchange/requests/publication', $payload, 30);
                if (is_wp_error($broker_result)) {
                    return new WP_Error('broker_error', $this->aysa_humanize_broker_error($broker_result, 'Could not submit publication proof.'));
                }
                $this->aysa_wallet_track_action_usage(
                    $project_id,
                    'aysa_adverlink_exchange_submit_publication',
                    'offpage_exchange_submit_publication_' . md5((string) ($payload['exchange_ref'] ?? '') . '|' . (string) ($payload['published_url'] ?? '')),
                    array('module' => 'offpage', 'exchange_ref' => (string) ($payload['exchange_ref'] ?? '')),
                    8,
                    'wp_ajax/aysa_adverlink_exchange_submit_publication'
                );
                return array(
                    'request' => isset($broker_result['request']) && is_array($broker_result['request']) ? $broker_result['request'] : array(),
                );
        }

        return new WP_Error('unsupported_task', 'Unsupported off-page task.');
    }

    public function handle_offpage_async_start() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $task = sanitize_key((string) wp_unslash($_POST['task'] ?? ''));
        if (!in_array($task, $this->aysa_offpage_async_supported_tasks(), true)) {
            wp_send_json_error('Unsupported off-page task.');
            return;
        }

        $request = $this->aysa_offpage_async_prepare_request($task, $_POST);
        if (is_wp_error($request)) {
            wp_send_json_error($request->get_error_message());
            return;
        }

        $project_id = intval($request['project_id'] ?? 0);
        $request_hash = (string) ($request['request_hash'] ?? '');
        $existing = $this->aysa_offpage_async_get_status($project_id, $task);
        if (
            is_array($existing) &&
            !empty($existing['run_id']) &&
            in_array((string) ($existing['status'] ?? ''), array('queued', 'running'), true) &&
            (string) ($existing['request_hash'] ?? '') === $request_hash
        ) {
            wp_send_json_success($existing);
            return;
        }

        $run_id = uniqid('offpage_', true);
        $status = array(
            'task' => $task,
            'project_id' => $project_id,
            'run_id' => $run_id,
            'request_hash' => $request_hash,
            'status' => 'queued',
            'result' => null,
            'error' => '',
            'started_at' => time(),
            'updated_at' => time(),
            'finished_at' => 0,
        );
        $this->aysa_offpage_async_save_status($project_id, $task, $status);
        $this->aysa_offpage_async_schedule_event(array(
            'task' => $task,
            'project_id' => $project_id,
            'run_id' => $run_id,
            'request_hash' => $request_hash,
            'request' => $request,
        ), 1);

        wp_send_json_success($status);
    }

    public function handle_offpage_async_status() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $task = sanitize_key((string) wp_unslash($_POST['task'] ?? ''));
        $project_id = $this->aysa_resolve_project_id_with_local_fallback(intval($_POST['project_id'] ?? get_option('aysa_project_id', 0)));
        if ($project_id <= 0 || !in_array($task, $this->aysa_offpage_async_supported_tasks(), true)) {
            wp_send_json_error('Invalid off-page task status request.');
            return;
        }

        $status = $this->aysa_offpage_async_get_status($project_id, $task);
        if (empty($status)) {
            wp_send_json_success(array(
                'task' => $task,
                'project_id' => $project_id,
                'status' => 'idle',
                'result' => null,
                'error' => '',
            ));
            return;
        }

        wp_send_json_success($status);
    }

    public function run_offpage_async_event($payload) {
        if (!is_array($payload)) {
            return;
        }

        $task = sanitize_key((string) ($payload['task'] ?? ''));
        $project_id = $this->aysa_resolve_installed_project_id(intval($payload['project_id'] ?? 0));
        $run_id = (string) ($payload['run_id'] ?? '');
        $request_hash = (string) ($payload['request_hash'] ?? '');
        $request = isset($payload['request']) && is_array($payload['request']) ? $payload['request'] : array();
        if (
            $project_id <= 0 ||
            intval($request['project_id'] ?? 0) !== $project_id ||
            $run_id === '' ||
            $request_hash === '' ||
            !in_array($task, $this->aysa_offpage_async_supported_tasks(), true)
        ) {
            return;
        }

        $status = $this->aysa_offpage_async_get_status($project_id, $task);
        if (
            empty($status) ||
            (string) ($status['run_id'] ?? '') !== $run_id ||
            (string) ($status['request_hash'] ?? '') !== $request_hash
        ) {
            return;
        }

        $status['status'] = 'running';
        $status['updated_at'] = time();
        $status['error'] = '';
        $this->aysa_offpage_async_save_status($project_id, $task, $status);

        $result = $this->aysa_offpage_async_execute($request);
        $status['updated_at'] = time();
        $status['finished_at'] = time();
        if (is_wp_error($result)) {
            $status['status'] = 'failed';
            $status['result'] = null;
            $status['error'] = $result->get_error_message();
        } else {
            $status['status'] = 'completed';
            $status['result'] = $result;
            $status['error'] = '';
        }
        $this->aysa_offpage_async_save_status($project_id, $task, $status);
    }

    public function handle_competitor_gap_keywords_gap() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        $force_refresh = !empty($_POST['force_refresh']) && intval(wp_unslash($_POST['force_refresh'])) === 1;

        $target = sanitize_text_field((string) wp_unslash($_POST['target'] ?? ''));
        if ($target === '') {
            $target = (string) wp_parse_url(home_url('/'), PHP_URL_HOST);
        }
        $location_name = sanitize_text_field((string) wp_unslash($_POST['location_name'] ?? 'Romania'));
        $language_name = sanitize_text_field((string) wp_unslash($_POST['language_name'] ?? 'Romanian'));
        $limit = max(1, min(2000, intval(wp_unslash($_POST['limit'] ?? 200))));

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $competitors_raw = wp_unslash($_POST['competitors'] ?? array());
        if (is_string($competitors_raw)) {
            $decoded = json_decode(wp_unslash($competitors_raw), true);
            $competitors_raw = is_array($decoded) ? $decoded : array();
        } elseif (is_array($competitors_raw)) {
            $competitors_raw = array_map('wp_unslash', $competitors_raw);
        } else {
            $competitors_raw = array();
        }

        $competitors = array();
        foreach ($competitors_raw as $competitor) {
            $value = trim(sanitize_text_field((string) $competitor));
            if ($value !== '') {
                $competitors[] = $value;
            }
        }
        $competitors = array_values(array_unique($competitors));

        $context = $this->aysa_monitoring_resolve_context($target);
        $resolved_target = trim((string) ($context['target'] ?? ''));
        if ($resolved_target === '') {
            $resolved_target = $target;
        }

        $normalized_competitors = array();
        foreach ($competitors as $competitor) {
            $host = $this->aysa_monitoring_extract_host((string) $competitor);
            if ($host === '' || $this->aysa_monitoring_is_local_host($host)) {
                if ($resolved_target !== '') {
                    $host = $resolved_target;
                }
            }
            if ($host !== '') {
                $normalized_competitors[] = $host;
            }
        }
        $normalized_competitors = array_values(array_unique($normalized_competitors));
        if (empty($normalized_competitors) && $resolved_target !== '') {
            $normalized_competitors[] = $resolved_target;
        }

        $gap_payload = array(
            'project_id' => $project_id,
            'target' => $resolved_target,
            'location_code' => intval($context['location_code'] ?? 2840),
            'language_code' => (string) ($context['language_code'] ?? 'ro'),
            'limit' => $limit,
            'competitors' => $normalized_competitors,
        );

        if (!$force_refresh) {
            $cached = $this->aysa_competitor_gap_get_cached_payload($project_id);
            $cached_request = is_array($cached) && isset($cached['request']) && is_array($cached['request'])
                ? $cached['request']
                : array();
            if (
                is_array($cached) &&
                !empty($cached['data']) &&
                is_array($cached['data']) &&
                $this->aysa_competitor_gap_requests_match($cached_request, $gap_payload)
            ) {
                $cached_data = $cached['data'];
                $cached_data['cached'] = true;
                $cached_data['cached_at'] = intval($cached['cached_at'] ?? 0);
                $cached_data['inKeywordResearch'] = $this->aysa_keyword_discovery_existing_keywords_map(
                    $project_id,
                    isset($cached_data['rankedKeywords']) && is_array($cached_data['rankedKeywords'])
                        ? $cached_data['rankedKeywords']
                        : array()
                );
                // Local/cache read must not consume credits.
                wp_send_json_success($cached_data);
                return;
            }
        }

        $broker_paths = array(
            '/v1/dataforseo/keywords/gap',
            '/v1/keywords/gap',
        );

        foreach ($broker_paths as $broker_path) {
            $broker_result = $this->aysa_broker_request('POST', $broker_path, $gap_payload, 180);
            if (is_wp_error($broker_result)) {
                continue;
            }

            if (is_array($broker_result) && !empty($broker_result['ok']) && isset($broker_result['rankedKeywords'])) {
                $this->aysa_competitor_gap_save_cached_payload($project_id, $gap_payload, $broker_result);
                $broker_result['cached'] = false;
                $broker_result['cached_at'] = time();
                $broker_result['inKeywordResearch'] = $this->aysa_keyword_discovery_existing_keywords_map(
                    $project_id,
                    isset($broker_result['rankedKeywords']) && is_array($broker_result['rankedKeywords'])
                        ? $broker_result['rankedKeywords']
                        : array()
                );
                $this->aysa_wallet_track_action_usage(
                    $project_id,
                    'aysa_competitor_gap_keywords_gap',
                    'competitor_gap_keywords_gap',
                    array(
                        'module' => 'competitor_gap',
                        'source' => 'broker',
                        'broker_path' => $broker_path,
                        'target' => $target,
                        'competitors_count' => count($competitors),
                    ),
                    35,
                    (string) $broker_path
                );
                wp_send_json_success($broker_result);
                return;
            }

            if (is_array($broker_result) && isset($broker_result['data']) && is_array($broker_result['data']) && isset($broker_result['data']['rankedKeywords'])) {
                $data = $broker_result['data'];
                $this->aysa_competitor_gap_save_cached_payload($project_id, $gap_payload, $data);
                $data['cached'] = false;
                $data['cached_at'] = time();
                $data['inKeywordResearch'] = $this->aysa_keyword_discovery_existing_keywords_map(
                    $project_id,
                    isset($data['rankedKeywords']) && is_array($data['rankedKeywords'])
                        ? $data['rankedKeywords']
                        : array()
                );
                $this->aysa_wallet_track_action_usage(
                    $project_id,
                    'aysa_competitor_gap_keywords_gap',
                    'competitor_gap_keywords_gap',
                    array(
                        'module' => 'competitor_gap',
                        'source' => 'broker_data',
                        'broker_path' => $broker_path,
                        'target' => $target,
                        'competitors_count' => count($competitors),
                    ),
                    35,
                    (string) $broker_path
                );
                wp_send_json_success($data);
                return;
            }
        }

        // Legacy safety fallback while old endpoint still exists.
        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }

        $query = array(
            'target' => $target,
            'locationName' => $location_name,
            'languageName' => $language_name,
            'ProjectId' => $project_id,
            'limit' => $limit,
        );
        if (!empty($competitors)) {
            $query['competitors'] = $competitors;
        }

        try {
            $auth_manager = Aysa_Auth_Manager::get_instance();
            $endpoint = 'api/keywordsgap/keywords-gap?' . http_build_query($query);
            $raw = $auth_manager->make_authenticated_request($endpoint, 'GET');
            if ($raw === false) {
                wp_send_json_error('Keywords gap endpoint returned empty response.');
                return;
            }

            if (is_string($raw)) {
                $decoded = json_decode($raw, true);
                if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                    $raw = $decoded;
                }
            }

            if (!is_array($raw)) {
                wp_send_json_error('Invalid keywords gap response format.');
                return;
            }

            if (is_array($raw) && isset($raw['rankedKeywords'])) {
                $this->aysa_competitor_gap_save_cached_payload($project_id, $gap_payload, $raw);
                $raw['cached'] = false;
                $raw['cached_at'] = time();
                $raw['inKeywordResearch'] = $this->aysa_keyword_discovery_existing_keywords_map(
                    $project_id,
                    is_array($raw['rankedKeywords']) ? $raw['rankedKeywords'] : array()
                );
            }
            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_competitor_gap_keywords_gap',
                'competitor_gap_keywords_gap',
                array(
                    'module' => 'competitor_gap',
                    'source' => 'legacy_remote',
                    'target' => $target,
                    'competitors_count' => count($competitors),
                ),
                35,
                '/v1/keywords/gap'
            );
            wp_send_json_success($raw);
        } catch (Exception $e) {
            wp_send_json_error('Failed to fetch keywords gap data: ' . $e->getMessage());
        }
    }

    public function handle_competitor_identification_seed_competitors() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        $force_refresh = !empty($_POST['force_refresh']) && intval(wp_unslash($_POST['force_refresh'])) === 1;
        $top_n = intval(wp_unslash($_POST['top_n'] ?? 10));
        if ($top_n <= 0) {
            $top_n = 10;
        }
        $top_n = min($top_n, 20);

        $seed_user_id = get_current_user_id();
        $seed_data = $this->aysa_keyword_strategy_extract_seed_data($seed_user_id);
        $seed_keywords = isset($seed_data['seed_keywords']) && is_array($seed_data['seed_keywords']) ? array_values($seed_data['seed_keywords']) : array();
        if (empty($seed_keywords)) {
            $shared_user_id = intval(get_option('aysa_user_id', 0));
            if ($shared_user_id > 0 && $shared_user_id !== $seed_user_id) {
                $shared_seed_data = $this->aysa_keyword_strategy_extract_seed_data($shared_user_id);
                $shared_seed_keywords = isset($shared_seed_data['seed_keywords']) && is_array($shared_seed_data['seed_keywords'])
                    ? array_values($shared_seed_data['seed_keywords'])
                    : array();
                if (!empty($shared_seed_keywords)) {
                    $seed_keywords = $shared_seed_keywords;
                }
            }
        }
        $seed_keywords = array_values(array_filter(array_map(function($kw) {
            return trim((string) $kw);
        }, $seed_keywords), function($kw) {
            return $kw !== '';
        }));

        $configured_competitors = isset($seed_data['competitors']) && is_array($seed_data['competitors']) ? array_values($seed_data['competitors']) : array();
        if (empty($configured_competitors)) {
            $shared_user_id = intval(get_option('aysa_user_id', 0));
            if ($shared_user_id > 0 && $shared_user_id !== $seed_user_id) {
                $shared_seed_data = $this->aysa_keyword_strategy_extract_seed_data($shared_user_id);
                $shared_competitors = isset($shared_seed_data['competitors']) && is_array($shared_seed_data['competitors'])
                    ? array_values($shared_seed_data['competitors'])
                    : array();
                if (!empty($shared_competitors)) {
                    $configured_competitors = $shared_competitors;
                }
            }
        }
        $configured_competitors = array_values(array_unique(array_filter(array_map(function($domain) {
            return $this->aysa_monitoring_extract_host((string) $domain);
        }, $configured_competitors), function($domain) {
            return $domain !== '';
        })));

        if (empty($seed_keywords)) {
            wp_send_json_error('No seed keywords found in Setup SEO Profile.');
            return;
        }

        $home_host = $this->aysa_monitoring_extract_host((string) home_url('/'));
        $resolved_context = $this->aysa_monitoring_resolve_context($home_host);
        $location_code = intval($resolved_context['location_code'] ?? 2840);
        if ($location_code <= 0) {
            $location_code = 2840;
        }
        $language_code = strtolower(trim((string) ($resolved_context['language_code'] ?? 'ro')));
        if ($language_code === '') {
            $language_code = 'ro';
        }
        $target_host = trim((string) ($resolved_context['target'] ?? ''));
        $own_domains = array();
        if ($home_host !== '') {
            $own_domains[] = strtolower($home_host);
        }
        if ($target_host !== '') {
            $own_domains[] = strtolower($target_host);
        }
        $own_domains = array_values(array_unique(array_filter($own_domains)));

        $request_payload = array(
            'project_id' => $project_id,
            'seed_keywords' => array_values($seed_keywords),
            'active_competitors' => array_values($configured_competitors),
            'location_code' => $location_code,
            'language_code' => $language_code,
            'top_n' => $top_n,
            'own_domains' => $own_domains,
        );

        if (!$force_refresh) {
            $cached = $this->aysa_competitor_identification_get_cached_payload($project_id);
            if (is_array($cached) && !empty($cached['data']) && is_array($cached['data'])) {
                $cached_request = isset($cached['request']) && is_array($cached['request']) ? $cached['request'] : array();
                $same_seeds = isset($cached_request['seed_keywords']) && is_array($cached_request['seed_keywords'])
                    ? array_map('strtolower', array_values($cached_request['seed_keywords']))
                    : array();
                $current_seeds = array_map('strtolower', array_values($request_payload['seed_keywords']));
                sort($same_seeds);
                sort($current_seeds);
                if ($same_seeds === $current_seeds) {
                    $data = $cached['data'];
                    $data['cached'] = true;
                    $data['cached_at'] = intval($cached['cached_at'] ?? 0);
                    // Local/cache read must not consume credits.
                    wp_send_json_success($data);
                    return;
                }
            }
        }

        $broker_paths = array(
            '/v1/competitors/identify/from-seeds',
        );
        $last_error = '';
        foreach ($broker_paths as $broker_path) {
            $broker_result = $this->aysa_broker_request('POST', $broker_path, $request_payload, 180);
            if (is_wp_error($broker_result)) {
                $last_error = $broker_result->get_error_message();
                continue;
            }

            $data = $broker_result;
            if (isset($data['data']) && is_array($data['data'])) {
                $data = $data['data'];
            }
            if (is_array($data) && !empty($data['error'])) {
                $last_error = (string) $data['error'];
            }
            if (!is_array($data) || empty($data['ok'])) {
                if ($last_error === '') {
                    $last_error = 'Broker response missing "ok=true".';
                }
                continue;
            }
            if (!isset($data['unique_competitors']) || !is_array($data['unique_competitors'])) {
                $last_error = 'Broker response missing competitors list.';
                continue;
            }

            $data = $this->aysa_keyword_v2_finalize_snapshot_contract($project_id, 'competitor_identification_v2', $data, 'unique_competitors');

            $this->aysa_competitor_identification_save_cached_payload($project_id, $request_payload, $data);
            $data['cached'] = false;
            $data['cached_at'] = time();
            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_competitor_identification_seed_competitors',
                'competitor_identification_seed_competitors',
                array(
                    'module' => 'competitor_identification',
                    'source' => 'broker',
                    'broker_path' => $broker_path,
                    'seed_keywords_count' => count($seed_keywords),
                    'top_n' => $top_n,
                ),
                35,
                (string) $broker_path
            );
            wp_send_json_success($data);
            return;
        }

        $cached = $this->aysa_competitor_identification_get_cached_payload($project_id);
        if (is_array($cached) && !empty($cached['data']) && is_array($cached['data'])) {
            $data = $cached['data'];
            $data['cached'] = true;
            $data['cached_at'] = intval($cached['cached_at'] ?? 0);
            // Cached fallback is still local read; do not charge credits.
            wp_send_json_success($data);
            return;
        }

        $message = 'Could not identify competitors from seed keywords.';
        if ($last_error !== '') {
            $message .= ' ' . $last_error;
        }
        wp_send_json_error($message);
    }

    public function handle_competitor_backlinks_setup_competitors() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        $seed_user_id = get_current_user_id();
        $seed_data = $this->aysa_keyword_strategy_extract_seed_data($seed_user_id);
        $competitors = isset($seed_data['competitors']) && is_array($seed_data['competitors']) ? array_values($seed_data['competitors']) : array();

        if (empty($competitors)) {
            $shared_user_id = intval(get_option('aysa_user_id', 0));
            if ($shared_user_id > 0 && $shared_user_id !== $seed_user_id) {
                $shared_seed_data = $this->aysa_keyword_strategy_extract_seed_data($shared_user_id);
                $shared_competitors = isset($shared_seed_data['competitors']) && is_array($shared_seed_data['competitors'])
                    ? array_values($shared_seed_data['competitors'])
                    : array();
                if (!empty($shared_competitors)) {
                    $competitors = $shared_competitors;
                }
            }
        }

        $normalized = array();
        foreach ($competitors as $raw) {
            $host = $this->aysa_monitoring_extract_host((string) $raw);
            if ($host === '') {
                $host = strtolower(trim((string) $raw));
            }
            $host = preg_replace('/^www\./', '', $host);
            if ($host === '' || !preg_match('/^[a-z0-9.-]+\.[a-z]{2,}$/', $host)) {
                continue;
            }
            $normalized[$host] = true;
        }

        $domains = array_keys($normalized);
        wp_send_json_success(array(
            'project_id' => $project_id,
            'competitors' => $domains,
            'count' => count($domains),
            'source' => 'setup_seo_profile',
        ));
    }

    private function aysa_competitor_identification_cache_key($project_id) {
        return 'aysa_competitor_identification_cache_' . intval($project_id);
    }

    private function aysa_competitor_identification_get_cached_payload($project_id) {
        $raw = get_option($this->aysa_competitor_identification_cache_key($project_id), array());
        return is_array($raw) ? $raw : array();
    }

    private function aysa_competitor_identification_save_cached_payload($project_id, $request_payload, $data_payload) {
        $record = array(
            'project_id' => intval($project_id),
            'cached_at' => time(),
            'request' => is_array($request_payload) ? $request_payload : array(),
            'data' => is_array($data_payload) ? $data_payload : array(),
        );
        update_option($this->aysa_competitor_identification_cache_key($project_id), $record, false);
    }

    private function aysa_competitor_gap_cache_key($project_id) {
        return 'aysa_competitor_gap_cache_' . intval($project_id);
    }

    private function aysa_competitor_gap_get_cached_payload($project_id) {
        $raw = get_option($this->aysa_competitor_gap_cache_key($project_id), array());
        return is_array($raw) ? $raw : array();
    }

    private function aysa_competitor_gap_save_cached_payload($project_id, $request_payload, $data_payload) {
        $record = array(
            'project_id' => intval($project_id),
            'cached_at' => time(),
            'request' => is_array($request_payload) ? $request_payload : array(),
            'data' => is_array($data_payload) ? $data_payload : array(),
        );
        update_option($this->aysa_competitor_gap_cache_key($project_id), $record, false);
    }

    private function aysa_competitor_gap_requests_match($left, $right) {
        if (!is_array($left) || !is_array($right)) {
            return false;
        }

        $normalize = function($payload) {
            $competitors = isset($payload['competitors']) && is_array($payload['competitors']) ? $payload['competitors'] : array();
            $normalized_competitors = array();
            foreach ($competitors as $competitor) {
                $host = strtolower(trim((string) $competitor));
                if ($host !== '') {
                    $normalized_competitors[] = $host;
                }
            }
            sort($normalized_competitors);

            return array(
                'target' => strtolower(trim((string) ($payload['target'] ?? ''))),
                'location_code' => intval($payload['location_code'] ?? 0),
                'language_code' => strtolower(trim((string) ($payload['language_code'] ?? ''))),
                'limit' => intval($payload['limit'] ?? 0),
                'competitors' => $normalized_competitors,
            );
        };

        return $normalize($left) === $normalize($right);
    }

    private function aysa_keyword_discovery_existing_keywords_map($project_id, $keywords) {
        $project_id = intval($project_id);
        if ($project_id <= 0 || !is_array($keywords) || empty($keywords)) {
            return array();
        }

        global $wpdb;
        $table = esc_sql($this->aysa_keyword_discovery_table());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $rows = $wpdb->get_col($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT keyword FROM {$table} WHERE project_id = %d",
            $project_id
        ));
        if (!is_array($rows)) {
            $rows = array();
        }

        $existing = array();
        foreach ($rows as $row_keyword) {
            $norm = strtolower(trim((string) $row_keyword));
            if ($norm !== '') {
                $existing[$norm] = true;
            }
        }

        $out = array();
        foreach ($keywords as $kw) {
            $keyword = trim((string) $kw);
            if ($keyword === '') {
                continue;
            }
            $out[$keyword] = !empty($existing[strtolower($keyword)]);
        }
        return $out;
    }

    private function aysa_agent8_get_setup_snapshot($project_id, $setup_user_id = 0) {
        $project_id = intval($project_id);
        $user_id = intval($setup_user_id);
        if ($user_id <= 0) {
            $user_id = intval($this->aysa_get_effective_setup_user_id());
        }

        $local_setup_state = $this->aysa_get_local_setup_state_for_user($user_id, $project_id);
        foreach (array('general', 'objectives', 'seo-settings', 'competition', 'tone-of-voice') as $step_key) {
            $legacy = array();
            if ($user_id > 0) {
                $legacy = get_option('aysa_wizard_' . $step_key . '_' . $user_id, array());
            }
            if ((!is_array($legacy) || empty($legacy)) && $project_id > 0) {
                $legacy = get_option('aysa_wizard_' . $step_key . '_project_' . $project_id, array());
            }
            if (!is_array($legacy) || empty($legacy)) {
                continue;
            }
            $existing_step = isset($local_setup_state[$step_key]) && is_array($local_setup_state[$step_key])
                ? $local_setup_state[$step_key]
                : array();
            $local_setup_state[$step_key] = array_replace($legacy, $existing_step);
        }
        $google_step = isset($local_setup_state['integrations']) && is_array($local_setup_state['integrations'])
            ? $local_setup_state['integrations']
            : array();

        return array(
            'local_setup_state' => $local_setup_state,
            'activation' => array(
                'project_id' => $project_id,
                'canonical_project_id' => intval(get_option('aysa_project_id', 0)),
                'website_id' => intval(get_option('aysa_website_id', 0)),
                'verified' => $this->aysa_get_effective_access_key('') !== '',
            ),
            'google_context' => array(
                'connected' => !empty($google_step['confirmed_with_google']) || !empty($google_step['gsc_connected']),
                'email' => (string) ($google_step['google_email'] ?? ''),
                'selected_gsc_site' => (string) ($google_step['gsc_site'] ?? ''),
                'selected_ga4_property' => (string) ($google_step['ga4_property'] ?? ''),
                'gsc_sites_count' => intval($google_step['gsc_sites_count'] ?? 0),
                'ga4_properties_count' => intval($google_step['ga4_properties_count'] ?? 0),
                'capabilities' => isset($google_step['capabilities']) && is_array($google_step['capabilities'])
                    ? $google_step['capabilities']
                    : array(),
                'granted_scopes' => isset($google_step['granted_scopes']) && is_array($google_step['granted_scopes'])
                    ? array_values($google_step['granted_scopes'])
                    : array(),
            ),
        );
    }

    public function handle_competitor_gap_add_to_keyword_research() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $raw_keywords = wp_unslash($_POST['keywords'] ?? array());
        if (is_string($raw_keywords)) {
            $decoded = json_decode(wp_unslash($raw_keywords), true);
            $raw_keywords = is_array($decoded) ? $decoded : array();
        } elseif (is_array($raw_keywords)) {
            $raw_keywords = array_map('wp_unslash', $raw_keywords);
        } else {
            $raw_keywords = array();
        }

        $keywords = array();
        foreach ($raw_keywords as $kw) {
            $text = trim(sanitize_text_field((string) $kw));
            if ($text !== '') {
                $keywords[] = $text;
            }
        }
        $keywords = array_values(array_unique($keywords));
        if (empty($keywords)) {
            wp_send_json_error('No keywords provided');
            return;
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $raw_volume_map = wp_unslash($_POST['volumes'] ?? array());
        if (is_string($raw_volume_map)) {
            $decoded = json_decode(wp_unslash($raw_volume_map), true);
            $raw_volume_map = is_array($decoded) ? $decoded : array();
        } elseif (!is_array($raw_volume_map)) {
            $raw_volume_map = array();
        }

        $volume_map = array();
        foreach ($raw_volume_map as $kw => $value) {
            $keyword = trim((string) $kw);
            if ($keyword === '') {
                continue;
            }
            $parsed = $this->aysa_keyword_discovery_parse_numeric($value);
            $volume_map[$keyword] = ($parsed === null ? null : intval(round($parsed)));
        }

        $context = $this->aysa_monitoring_resolve_context('');
        $metrics_map = $this->aysa_keyword_discovery_fetch_keyword_metrics($project_id, $keywords, $context);
        $intent_map = $this->aysa_keyword_discovery_fetch_keyword_intent_bulk($project_id, $keywords, $context);
        if (!is_array($metrics_map)) {
            $metrics_map = array();
        }
        if (!is_array($intent_map)) {
            $intent_map = array();
        }

        $rows = array();
        $normalized_keywords = array();
        foreach ($keywords as $kw) {
            $norm = strtolower(trim((string) $kw));
            if ($norm === '') {
                continue;
            }
            $normalized_keywords[$norm] = $kw;

            $m = isset($metrics_map[$norm]) && is_array($metrics_map[$norm]) ? $metrics_map[$norm] : array();
            $i = isset($intent_map[$norm]) && is_array($intent_map[$norm]) ? $intent_map[$norm] : array();

            $search_volume = null;
            if (isset($volume_map[$kw]) && $volume_map[$kw] !== null) {
                $search_volume = intval($volume_map[$kw]);
            } elseif (isset($m['search_volume']) && $m['search_volume'] !== null) {
                $search_volume = intval($m['search_volume']);
            }

            $rows[] = array(
                'keyword' => $kw,
                'searchVolume' => $search_volume,
                'difficulty' => (isset($m['kd']) && $m['kd'] !== null) ? floatval($m['kd']) : null,
                'intent' => (string) (($i['intent'] ?? $m['intent'] ?? '')),
                'intent_probabilities' => $i['intent_probabilities'] ?? array(),
                'intent_source' => (string) ($i['intent_source'] ?? 'bulk_intent'),
                'intent_updated_at' => (string) ($i['intent_updated_at'] ?? current_time('mysql')),
                'competition' => (string) ($m['competition'] ?? ''),
                'traffic' => (isset($m['traffic']) && $m['traffic'] !== null) ? intval($m['traffic']) : null,
                'trafficPct' => (isset($m['traffic_pct']) && $m['traffic_pct'] !== null) ? floatval($m['traffic_pct']) : null,
                'position' => (isset($m['position']) && $m['position'] !== null) ? floatval($m['position']) : null,
                'cpc' => (isset($m['cpc']) && $m['cpc'] !== null) ? floatval($m['cpc']) : null,
                'url' => (string) ($m['url'] ?? ''),
                'item_types' => (isset($m['item_types']) && is_array($m['item_types'])) ? $m['item_types'] : array(),
                'source' => 'competitor_gap',
            );
        }

        $persist_result = $this->aysa_keyword_discovery_persist_keywords($project_id, $rows);
        $in_map = $this->aysa_keyword_discovery_existing_keywords_map($project_id, $keywords);

        wp_send_json_success(array(
            'project_id' => $project_id,
            'total_keywords' => count($keywords),
            'persisted' => $persist_result,
            'inKeywordResearch' => $in_map,
        ));
    }

    private function aysa_keyword_discovery_table() {
        global $wpdb;
        return $wpdb->prefix . 'aysa_keyword_metrics';
    }

    public function aysa_keyword_discovery_maybe_create_table() {
        $this->aysa_keyword_discovery_create_table();
    }

    private function aysa_keyword_discovery_create_table() {
        global $wpdb;
        $table = esc_sql($this->aysa_keyword_discovery_table());
        $charset_collate = $wpdb->get_charset_collate();
        $sql = "CREATE TABLE {$table} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            project_id bigint(20) unsigned NOT NULL,
            keyword varchar(255) NOT NULL,
            search_volume int(11) DEFAULT NULL,
            difficulty decimal(6,2) DEFAULT NULL,
            intent varchar(64) DEFAULT NULL,
            intent_probabilities longtext NULL,
            intent_source varchar(32) DEFAULT NULL,
            intent_updated_at datetime DEFAULT NULL,
            competition varchar(64) DEFAULT NULL,
            traffic int(11) DEFAULT NULL,
            traffic_pct decimal(10,4) DEFAULT NULL,
            position decimal(10,4) DEFAULT NULL,
            cpc decimal(10,4) DEFAULT NULL,
            target_url text NULL,
            item_types text NULL,
            source varchar(32) DEFAULT 'suggested',
            remote_keyword_id bigint(20) unsigned DEFAULT NULL,
            updated_at datetime NOT NULL,
            created_at datetime NOT NULL,
            PRIMARY KEY  (id),
            UNIQUE KEY project_keyword (project_id, keyword),
            KEY project_updated (project_id, updated_at),
            KEY project_remote (project_id, remote_keyword_id)
        ) {$charset_collate};";
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta($sql);
    }

    private function aysa_keyword_discovery_status_key($project_id) {
        return 'aysa_keyword_discovery_refresh_status_' . intval($project_id);
    }

    private function aysa_keyword_discovery_lock_key($project_id) {
        return 'aysa_keyword_discovery_refresh_lock_' . intval($project_id);
    }

    private function aysa_keyword_discovery_snapshot_key($project_id) {
        return 'aysa_keyword_discovery_snapshot_' . intval($project_id);
    }

    private function aysa_keyword_discovery_get_status($project_id) {
        $status = get_option($this->aysa_keyword_discovery_status_key($project_id), array());
        if (!is_array($status)) {
            $status = array();
        }
        return array(
            'run_id' => (string) ($status['run_id'] ?? ''),
            'refresh_kind' => (string) ($status['refresh_kind'] ?? 'gsc_auto'),
            'status' => (string) ($status['status'] ?? 'idle'),
            'total' => intval($status['total'] ?? 0),
            'processed' => intval($status['processed'] ?? 0),
            'staged' => intval($status['staged'] ?? 0),
            'inserted' => intval($status['inserted'] ?? 0),
            'updated' => intval($status['updated'] ?? 0),
            'apply_to_wp' => !empty($status['apply_to_wp']),
            'last_error' => (string) ($status['last_error'] ?? ''),
            'metrics_notice' => (string) ($status['metrics_notice'] ?? ''),
            'notification_sent' => (string) ($status['notification_sent'] ?? '0'),
            'started_at' => intval($status['started_at'] ?? 0),
            'updated_at' => intval($status['updated_at'] ?? 0),
            'finished_at' => intval($status['finished_at'] ?? 0),
        );
    }

    private function aysa_keyword_discovery_save_status($project_id, $status) {
        if (!is_array($status)) {
            return;
        }
        update_option($this->aysa_keyword_discovery_status_key($project_id), $status, false);
    }

    private function aysa_keyword_discovery_build_pending_item($project_id, $refresh_kind = 'gsc_auto') {
        $project_id = intval($project_id);
        $refresh_kind = (string) $refresh_kind;
        $title = $refresh_kind === 'keyword_suggestions' ? 'Keyword suggestions refresh' : 'Keyword refresh';
        return array(
            'item_key' => 'keyword_refresh:' . $refresh_kind . ':' . $project_id,
            'item_type' => 'keyword_refresh',
            'post_id' => $project_id,
            'title' => $title,
        );
    }

    private function aysa_keyword_discovery_start_background_refresh($project_id, $apply_to_wp = true, $refresh_kind = 'gsc_auto') {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return new WP_Error('invalid_project_id', 'Invalid project id');
        }
        $refresh_kind = strtolower(trim((string) $refresh_kind));
        if (!in_array($refresh_kind, array('gsc_auto', 'keyword_suggestions'), true)) {
            $refresh_kind = 'gsc_auto';
        }

        $existing_status = $this->aysa_keyword_discovery_get_status($project_id);
        if (($existing_status['status'] ?? '') === 'running') {
            $updated_at = intval($existing_status['updated_at'] ?? 0);
            $started_at = intval($existing_status['started_at'] ?? 0);
            $freshness_anchor = max($updated_at, $started_at);
            if ($freshness_anchor > 0 && (time() - $freshness_anchor) > 180) {
                $existing_status['status'] = 'failed';
                $existing_status['last_error'] = 'Keyword refresh stalled. Please retry. If this persists, reconnect Google Search Console.';
                $existing_status['updated_at'] = time();
                $existing_status['finished_at'] = time();
                $this->aysa_keyword_discovery_save_status($project_id, $existing_status);
            } else {
                return array(
                    'started' => false,
                    'already_running' => true,
                    'status' => $existing_status,
                    'pending_item' => $this->aysa_keyword_discovery_build_pending_item($project_id, (string) ($existing_status['refresh_kind'] ?? $refresh_kind)),
                );
            }
        }

        $run_id = uniqid('kwd_', true);
        $now = time();
        $status = array(
            'run_id' => $run_id,
            'refresh_kind' => $refresh_kind,
            'status' => 'running',
            'total' => 0,
            'processed' => 0,
            'inserted' => 0,
            'updated' => 0,
            'staged' => 0,
            'apply_to_wp' => $apply_to_wp ? 1 : 0,
            'last_error' => '',
            'metrics_notice' => '',
            'notification_sent' => '0',
            'started_at' => $now,
            'updated_at' => $now,
            'finished_at' => 0,
        );
        $this->aysa_keyword_discovery_save_status($project_id, $status);
        $this->aysa_keyword_discovery_schedule_background_refresh($project_id, 1);

        return array(
            'started' => true,
            'already_running' => false,
            'status' => $status,
            'pending_item' => $this->aysa_keyword_discovery_build_pending_item($project_id, $refresh_kind),
        );
    }

    private function aysa_keyword_discovery_schedule_background_refresh($project_id, $delay_seconds = 1) {
        $timestamp = time() + max(1, intval($delay_seconds));
        wp_schedule_single_event($timestamp, 'aysa_keyword_discovery_refresh_background_event', array(intval($project_id)));
        if (function_exists('spawn_cron')) {
            spawn_cron();
        }
    }

    private function aysa_keyword_discovery_parse_numeric($value) {
        if ($value === null || $value === '') {
            return null;
        }
        if (is_numeric($value)) {
            return (float) $value;
        }
        $normalized = str_replace(',', '.', (string) $value);
        return is_numeric($normalized) ? (float) $normalized : null;
    }

    private function aysa_keyword_discovery_parse_item_types($value) {
        if (is_array($value)) {
            $items = array_values(array_filter(array_map('strval', $value), function($v) {
                return trim($v) !== '';
            }));
            return $items;
        }
        if (is_string($value) && trim($value) !== '') {
            $decoded = json_decode($value, true);
            if (is_array($decoded)) {
                return array_values(array_filter(array_map('strval', $decoded), function($v) {
                    return trim($v) !== '';
                }));
            }
            return array_values(array_filter(array_map('trim', explode(',', $value)), function($v) {
                return $v !== '';
            }));
        }
        return array();
    }

    private function aysa_keyword_discovery_collect_legacy_keywords($limit = 1000) {
        global $wpdb;
        $legacy_table = esc_sql($wpdb->prefix . 'aysa_keywords');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $legacy_table));
        if ($table_exists !== $legacy_table) {
            return array();
        }

        $limit = max(1, min(5000, intval($limit)));
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $rows = $wpdb->get_results(
            $wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT value AS keyword, MAX(volume) AS searchVolume
" .
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
                "                 FROM {$legacy_table}
                 WHERE value IS NOT NULL AND TRIM(value) <> ''
                 GROUP BY value
                 ORDER BY MAX(update_at) DESC
                 LIMIT %d",
                $limit
            ),
            ARRAY_A
        );

        return is_array($rows) ? $rows : array();
    }

    private function aysa_keyword_discovery_persist_keywords($project_id, $rows) {
        global $wpdb;
        $table = esc_sql($this->aysa_keyword_discovery_table());
        $project_id = intval($project_id);
        if ($project_id <= 0 || !is_array($rows)) {
            return array('inserted' => 0, 'updated' => 0, 'total' => 0);
        }

        $inserted = 0;
        $updated = 0;
        $now = current_time('mysql');

        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keyword = trim((string) ($row['keyword'] ?? $row['Keyword'] ?? ''));
            if ($keyword === '') {
                continue;
            }
            $search_volume = $this->aysa_keyword_discovery_parse_numeric($row['searchVolume'] ?? $row['search_volume'] ?? null);
            $difficulty = $this->aysa_keyword_discovery_parse_numeric($row['difficulty'] ?? $row['keyword_difficulty'] ?? null);
            $intent = trim((string) ($row['intent'] ?? $row['Intent'] ?? ''));
            $intent_probabilities = $this->aysa_keyword_discovery_normalize_intent_probabilities(
                $row['intent_probabilities'] ?? $row['intentProbabilities'] ?? (isset($row['search_intent_info']) ? $row['search_intent_info'] : array())
            );
            $intent_probabilities_json = !empty($intent_probabilities) ? wp_json_encode($intent_probabilities) : null;
            $intent_source = trim((string) ($row['intent_source'] ?? $row['intentSource'] ?? ''));
            $intent_updated_at = trim((string) ($row['intent_updated_at'] ?? $row['intentUpdatedAt'] ?? ''));
            if ($intent_updated_at === '' && ($intent !== '' || !empty($intent_probabilities))) {
                $intent_updated_at = $now;
            }
            $competition = trim((string) ($row['competition'] ?? $row['competition_level'] ?? ''));
            $traffic = $this->aysa_keyword_discovery_parse_numeric($row['traffic'] ?? $row['gsc_clicks'] ?? null);
            $traffic_pct = $this->aysa_keyword_discovery_parse_numeric($row['traffic_pct'] ?? $row['trafficPct'] ?? $row['trafficPercent'] ?? null);
            $position = $this->aysa_keyword_discovery_parse_numeric($row['position'] ?? $row['rank_group'] ?? null);
            $cpc = $this->aysa_keyword_discovery_parse_numeric($row['cpc'] ?? $row['cpc_usd'] ?? $row['cpcUsd'] ?? null);
            $target_url = trim((string) ($row['url'] ?? $row['target_url'] ?? $row['link'] ?? ''));
            $item_types_array = $this->aysa_keyword_discovery_parse_item_types($row['item_types'] ?? $row['serp_features'] ?? array());
            $item_types_json = !empty($item_types_array) ? wp_json_encode($item_types_array) : null;
            $remote_id = intval($row['id'] ?? $row['Id'] ?? 0);

            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $existing_id = intval($wpdb->get_var($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT id FROM {$table} WHERE project_id = %d AND keyword = %s LIMIT 1",
                $project_id,
                $keyword
            )));

            $source = strtolower(trim((string) ($row['source'] ?? 'suggested')));
            if ($source === '') {
                $source = 'suggested';
            }
            if (!in_array($source, array('suggested', 'gsc', 'gsc_query', 'gsc_query_only', 'target', 'keyword_suggestion', 'related_keyword', 'association_seed', 'legacy_local_seed', 'competitor_gap'), true)) {
                $source = 'suggested';
            }

            $data = array(
                'project_id' => $project_id,
                'keyword' => $keyword,
                'search_volume' => ($search_volume === null ? null : intval(round($search_volume))),
                'difficulty' => ($difficulty === null ? null : round($difficulty, 2)),
                'intent' => ($intent !== '' ? $intent : null),
                'intent_probabilities' => $intent_probabilities_json,
                'intent_source' => ($intent_source !== '' ? $intent_source : null),
                'intent_updated_at' => ($intent_updated_at !== '' ? $intent_updated_at : null),
                'competition' => ($competition !== '' ? $competition : null),
                'traffic' => ($traffic === null ? null : intval(round($traffic))),
                'traffic_pct' => ($traffic_pct === null ? null : round($traffic_pct, 4)),
                'position' => ($position === null ? null : round($position, 4)),
                'cpc' => ($cpc === null ? null : round($cpc, 4)),
                'target_url' => ($target_url !== '' ? $target_url : null),
                'item_types' => $item_types_json,
                'source' => $source,
                'remote_keyword_id' => ($remote_id > 0 ? $remote_id : null),
                'updated_at' => $now,
            );

            if ($existing_id > 0) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $wpdb->update(
                    $table,
                    $data,
                    array('id' => $existing_id),
                    array('%d', '%s', '%d', '%f', '%s', '%s', '%s', '%s', '%s', '%d', '%f', '%f', '%f', '%s', '%s', '%s', '%d', '%s'),
                    array('%d')
                );
                $updated++;
            } else {
                $data['created_at'] = $now;
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $wpdb->insert(
                    $table,
                    $data,
                    array('%d', '%s', '%d', '%f', '%s', '%s', '%s', '%s', '%s', '%d', '%f', '%f', '%f', '%s', '%s', '%s', '%d', '%s', '%s')
                );
                $inserted++;
            }
        }

        return array(
            'inserted' => $inserted,
            'updated' => $updated,
            'total' => $inserted + $updated,
        );
    }

    private function aysa_keyword_discovery_clear_suggestions_rows($project_id) {
        global $wpdb;
        $table = esc_sql($this->aysa_keyword_discovery_table());
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return 0;
        }
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        return intval($wpdb->query($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "DELETE FROM {$table} WHERE project_id = %d AND source IN (%s, %s)",
            $project_id,
            'keyword_suggestion',
            'related_keyword'
        )));
    }

    private function aysa_keyword_discovery_clear_gsc_rows($project_id) {
        global $wpdb;
        $table = esc_sql($this->aysa_keyword_discovery_table());
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return 0;
        }
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        return intval($wpdb->query($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "DELETE FROM {$table} WHERE project_id = %d AND source IN (%s, %s, %s)",
            $project_id,
            'gsc',
            'gsc_query',
            'gsc_query_only'
        )));
    }

    public function handle_keyword_discovery_local_keywords() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_keyword_cache_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Access denied');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project ID');
            return;
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $source_mode = strtolower(trim((string) wp_unslash($_POST['source_mode'] ?? 'gsc_auto')));
        $keywords = $this->aysa_keyword_discovery_get_local_keywords_payload($project_id, $source_mode);

        $status = $this->aysa_keyword_discovery_get_status($project_id);
        $snapshot = get_option($this->aysa_keyword_discovery_snapshot_key($project_id), array());
        wp_send_json_success(array(
            'keywords' => $keywords,
            'count' => count($keywords),
            'status' => $status,
            'snapshot' => array(
                'fetched_at' => intval($snapshot['fetched_at'] ?? 0),
                'count' => intval($snapshot['count'] ?? 0),
                'run_id' => (string) ($snapshot['run_id'] ?? ''),
            ),
        ));
    }

    private function aysa_keyword_discovery_get_local_keywords_payload($project_id, $source_mode = 'gsc_auto') {
        global $wpdb;
        $table = esc_sql($this->aysa_keyword_discovery_table());
        $project_id = intval($project_id);
        $source_mode = strtolower(trim((string) $source_mode));
        if ($project_id <= 0) {
            return array();
        }

        $where_sql = "project_id = %d";
        $prepare_args = array($project_id);
        if ($source_mode === 'keyword_suggestions') {
            $where_sql .= " AND source IN (%s, %s)";
            $prepare_args[] = 'keyword_suggestion';
            $prepare_args[] = 'related_keyword';
        } elseif ($source_mode === 'gsc_auto') {
            $where_sql .= " AND source IN (%s, %s, %s)";
            $prepare_args[] = 'gsc';
            $prepare_args[] = 'gsc_query';
            $prepare_args[] = 'gsc_query_only';
        } else {
            $where_sql .= " AND (source IS NULL OR source NOT IN (%s, %s))";
            $prepare_args[] = 'keyword_suggestion';
            $prepare_args[] = 'related_keyword';
        }

        $query = "SELECT keyword, search_volume, difficulty, intent, intent_probabilities, intent_source, intent_updated_at, competition, traffic, traffic_pct, position, cpc, target_url, item_types, remote_keyword_id, source, updated_at
             FROM {$table}
             WHERE {$where_sql}
             ORDER BY updated_at DESC, id DESC
             LIMIT 5000";
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query: table identifiers and structure are internal, and request-derived values are prepared before execution. Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $rows = $wpdb->get_results($wpdb->prepare($query, $prepare_args), ARRAY_A);
        if (!is_array($rows)) {
            return array();
        }

        $keywords = array();
        foreach ($rows as $row) {
            $item_types = $this->aysa_keyword_discovery_parse_item_types($row['item_types'] ?? array());
            $intent_probabilities = $this->aysa_keyword_discovery_normalize_intent_probabilities($row['intent_probabilities'] ?? array());
            $keywords[] = array(
                'keyword' => (string) ($row['keyword'] ?? ''),
                'searchVolume' => ($row['search_volume'] !== null ? intval($row['search_volume']) : 'N/A'),
                'difficulty' => ($row['difficulty'] !== null ? (float) $row['difficulty'] : 'N/A'),
                'intent' => ((string) ($row['intent'] ?? '') !== '' ? (string) $row['intent'] : 'N/A'),
                'intentProbabilities' => $intent_probabilities,
                'intentSource' => ((string) ($row['intent_source'] ?? '') !== '' ? (string) $row['intent_source'] : ''),
                'intentUpdatedAt' => ((string) ($row['intent_updated_at'] ?? '') !== '' ? (string) $row['intent_updated_at'] : ''),
                'competition' => ((string) ($row['competition'] ?? '') !== '' ? (string) $row['competition'] : 'N/A'),
                'traffic' => ($row['traffic'] !== null ? intval($row['traffic']) : 'N/A'),
                'trafficPct' => ($row['traffic_pct'] !== null ? floatval($row['traffic_pct']) : 'N/A'),
                'position' => ($row['position'] !== null ? floatval($row['position']) : 'N/A'),
                'cpc' => ($row['cpc'] !== null ? floatval($row['cpc']) : 'N/A'),
                'url' => ((string) ($row['target_url'] ?? '') !== '' ? (string) $row['target_url'] : 'N/A'),
                'itemTypes' => $item_types,
                'id' => intval($row['remote_keyword_id'] ?? 0),
                'projectId' => $project_id,
                'source' => (string) ($row['source'] ?? 'suggested'),
                'updatedAt' => (string) ($row['updated_at'] ?? ''),
                'createdDate' => (string) ($row['updated_at'] ?? ''),
            );
        }

        return $keywords;
    }

    public function handle_keyword_discovery_intent_batch() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Access denied');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project ID');
            return;
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $raw_keywords = wp_unslash($_POST['keywords'] ?? array());
        if (is_string($raw_keywords)) {
            $decoded = json_decode(wp_unslash($raw_keywords), true);
            $raw_keywords = is_array($decoded) ? $decoded : array();
        } elseif (is_array($raw_keywords)) {
            $raw_keywords = array_map('wp_unslash', $raw_keywords);
        } else {
            $raw_keywords = array();
        }

        $keywords = array();
        foreach ($raw_keywords as $kw) {
            $text = trim(sanitize_text_field((string) $kw));
            if ($text !== '') {
                $keywords[] = $text;
            }
        }
        $keywords = array_values(array_unique($keywords));
        if (empty($keywords)) {
            wp_send_json_success(array('intents' => array()));
            return;
        }

        $context = $this->aysa_monitoring_resolve_context('');
        $intent_map = $this->aysa_keyword_discovery_fetch_keyword_intent_bulk($project_id, $keywords, $context);
        if (!is_array($intent_map)) {
            $intent_map = array();
        }

        if (!empty($intent_map)) {
            global $wpdb;
            $table = esc_sql($this->aysa_keyword_discovery_table());
            $now = current_time('mysql');
            $keyword_by_norm = array();
            foreach ($keywords as $kw) {
                $keyword_by_norm[strtolower(trim((string) $kw))] = (string) $kw;
            }

            foreach ($intent_map as $norm_keyword => $intent_row) {
                if (!is_array($intent_row)) {
                    continue;
                }
                $norm_keyword = strtolower(trim((string) $norm_keyword));
                if ($norm_keyword === '' || !isset($keyword_by_norm[$norm_keyword])) {
                    continue;
                }

                $intent = trim((string) ($intent_row['intent'] ?? ''));
                $intent_probs = $this->aysa_keyword_discovery_normalize_intent_probabilities(
                    $intent_row['intent_probabilities'] ?? array()
                );
                $intent_probs_json = !empty($intent_probs) ? wp_json_encode($intent_probs) : null;
                $intent_source = trim((string) ($intent_row['intent_source'] ?? 'bulk_intent'));
                $intent_updated_at = trim((string) ($intent_row['intent_updated_at'] ?? $now));

                if ($intent === '' && empty($intent_probs)) {
                    continue;
                }

                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $wpdb->update(
                    $table,
                    array(
                        'intent' => ($intent !== '' ? $intent : null),
                        'intent_probabilities' => $intent_probs_json,
                        'intent_source' => ($intent_source !== '' ? $intent_source : 'bulk_intent'),
                        'intent_updated_at' => ($intent_updated_at !== '' ? $intent_updated_at : $now),
                        'updated_at' => $now,
                    ),
                    array(
                        'project_id' => $project_id,
                        'keyword' => $keyword_by_norm[$norm_keyword],
                    ),
                    array('%s', '%s', '%s', '%s', '%s'),
                    array('%d', '%s')
                );
            }
        }

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_keyword_discovery_intent_batch',
            'keyword_discovery_intent_batch',
            array(
                'module' => 'keyword_discovery',
                'keywords_count' => count($keywords),
                'intents_count' => count($intent_map),
            ),
            30,
            '/v1/dataforseo/keywords/intent-bulk'
        );

        wp_send_json_success(array('intents' => $intent_map));
    }

    private function aysa_keyword_strategy_option_key($project_id, $user_id) {
        return 'aysa_keyword_strategy_' . intval($project_id) . '_' . intval($user_id);
    }

    private function aysa_keyword_strategy_normalize_keyword($keyword) {
        $value = strtolower(trim((string) $keyword));
        if ($value === '') {
            return '';
        }
        if (function_exists('remove_accents')) {
            $value = remove_accents($value);
        }
        $value = preg_replace('/\s+/', ' ', $value);
        return is_string($value) ? trim($value) : '';
    }

    private function aysa_keyword_strategy_extract_seed_data($user_id) {
        $option_name = 'aysa_wizard_competition_' . intval($user_id);
        $saved = get_option($option_name, array());
        if (!is_array($saved)) {
            $saved = array();
        }

        $seed_keywords = array();
        $competitors = array();

        foreach ($saved as $key => $value) {
            $val = trim((string) $value);
            if ($val === '') {
                continue;
            }
            if (preg_match('/^keyword\d+$/', (string) $key)) {
                $seed_keywords[] = $val;
                continue;
            }
            if (preg_match('/^competitor\d+$/', (string) $key)) {
                $competitors[] = $val;
                continue;
            }
        }

        $seed_keywords = array_values(array_unique(array_filter($seed_keywords, function($kw) {
            return trim((string) $kw) !== '';
        })));
        $competitors = array_values(array_unique(array_filter($competitors, function($url) {
            return trim((string) $url) !== '';
        })));

        return array(
            'seed_keywords' => $seed_keywords,
            'competitors' => $competitors,
            'raw' => $saved,
        );
    }

    private function aysa_keyword_strategy_get_context($user_id) {
        $ctx = array(
            'targeted_country' => 'Romania',
            'targeted_region' => '',
            'targeted_city' => '',
            'primary_language' => 'Romanian',
            'website_type' => '',
            'industry' => '',
        );

        $seo_settings = get_option('aysa_wizard_seo-settings_' . intval($user_id), array());
        if (is_array($seo_settings)) {
            $ctx['targeted_country'] = trim((string) ($seo_settings['targeted_country'] ?? $ctx['targeted_country']));
            $ctx['targeted_region'] = trim((string) ($seo_settings['targeted_region'] ?? ''));
            $ctx['targeted_city'] = trim((string) ($seo_settings['targeted_city'] ?? ''));
            $ctx['primary_language'] = trim((string) ($seo_settings['primary_language'] ?? $ctx['primary_language']));
        }

        $general = get_option('aysa_wizard_general_' . intval($user_id), array());
        if (is_array($general)) {
            $ctx['website_type'] = trim((string) ($general['website_type'] ?? ''));
            $ctx['industry'] = trim((string) ($general['industry'] ?? ''));
        }

        return $ctx;
    }

    private function aysa_keyword_strategy_get_association_map($project_id) {
        $rows = $this->get_keyword_discovery_local_associations($project_id);

        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }

        try {
            $auth_manager = Aysa_Auth_Manager::get_instance();
            $remote_raw = $auth_manager->make_authenticated_request(
                'api/Association?projectId=' . intval($project_id) . '&pageSize=1000&pageNumber=1&sortOrder=asc',
                'GET'
            );
            $remote_rows = $this->normalize_aysa_collection_response($remote_raw);
            if (is_array($remote_rows) && !empty($remote_rows)) {
                $rows = $this->merge_keyword_discovery_associations($remote_rows, $rows);
            }
        } catch (Exception $e) {
            // Keep local fallback only.
        }

        $map = array();
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keyword = $this->aysa_keyword_strategy_normalize_keyword($row['keyword'] ?? '');
            if ($keyword === '') {
                continue;
            }
            if (!isset($map[$keyword])) {
                $map[$keyword] = array();
            }
            $map[$keyword][] = array(
                'associatedOn' => (string) ($row['associatedOn'] ?? $row['associated_on'] ?? ''),
                'idIntern' => intval($row['idIntern'] ?? $row['id_intern'] ?? 0),
                'value' => (string) ($row['value'] ?? ''),
            );
        }

        return $map;
    }

    private function aysa_keyword_strategy_collect_local_keyword_rows($project_id) {
        global $wpdb;
        $table = esc_sql($this->aysa_keyword_discovery_table());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $rows = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT keyword, search_volume, difficulty, intent, competition, traffic, traffic_pct, position, cpc, target_url, updated_at
" .
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
            "             FROM {$table}
             WHERE project_id = %d
             ORDER BY updated_at DESC, id DESC
             LIMIT 5000",
            $project_id
        ), ARRAY_A);
        return is_array($rows) ? $rows : array();
    }

    public function handle_content_strategy_generate() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_content_strategy_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        $strategy_type = sanitize_key((string) wp_unslash($_POST['strategy_type'] ?? ''));
        if ($strategy_type === '') {
            wp_send_json_error('Missing strategy type');
            return;
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $raw_payload = wp_unslash($_POST['payload'] ?? array());
        if (is_string($raw_payload)) {
            $decoded = json_decode(wp_unslash($raw_payload), true);
            $raw_payload = is_array($decoded) ? $decoded : array();
        } elseif (is_array($raw_payload)) {
            $raw_payload = array_map('wp_unslash', $raw_payload);
        } else {
            $raw_payload = array();
        }

        $setup_user_id = intval($this->aysa_get_authenticated_external_user_id());
        if ($setup_user_id <= 0) {
            $setup_user_id = get_current_user_id();
        }

        $context_bundle = $this->aysa_collect_content_strategy_context_bundle($project_id, $setup_user_id);
        $result = $this->aysa_broker_request('POST', '/v1/content-strategy/generate', array(
            'project_id' => $project_id,
            'strategy_type' => $strategy_type,
            'raw_context' => $context_bundle,
            'payload_overrides' => $raw_payload,
        ), 120);
        if (is_wp_error($result)) {
            wp_send_json_error($result->get_error_message());
            return;
        }

        if (!is_array($result) || empty($result['ok'])) {
            wp_send_json_error(is_array($result) ? ($result['error'] ?? 'content_strategy_generation_failed') : 'content_strategy_generation_failed');
            return;
        }

        wp_send_json_success(array(
            'strategy_type' => (string) ($result['strategy_type'] ?? $strategy_type),
            'items' => (isset($result['items']) && is_array($result['items'])) ? $result['items'] : array(),
            'model' => (string) ($result['model'] ?? ''),
            'usage' => (isset($result['usage']) && is_array($result['usage'])) ? $result['usage'] : array(),
            'warnings' => (isset($result['warnings']) && is_array($result['warnings'])) ? $result['warnings'] : array(),
        ));
    }

    private function aysa_adjustments_generate_content_strategy_items($project_id, $strategy_type, $payload = array()) {
        $project_id = intval($project_id);
        $strategy_type = sanitize_key((string) $strategy_type);
        $payload = is_array($payload) ? $payload : array();
        if ($project_id <= 0 || $strategy_type === '') {
            return new WP_Error('invalid_content_strategy_request', 'Invalid content strategy request.');
        }

        $setup_user_id = intval($this->aysa_get_authenticated_external_user_id());
        if ($setup_user_id <= 0) {
            $setup_user_id = get_current_user_id();
        }

        $result = $this->aysa_broker_request('POST', '/v1/content-strategy/generate', array(
            'project_id' => $project_id,
            'strategy_type' => $strategy_type,
            'raw_context' => $this->aysa_collect_content_strategy_context_bundle($project_id, $setup_user_id),
            'payload_overrides' => $payload,
        ), 120);
        if (is_wp_error($result)) {
            return $result;
        }

        if (!is_array($result) || empty($result['ok'])) {
            return new WP_Error(
                'content_strategy_generation_failed',
                is_array($result) ? (string) ($result['error'] ?? 'content_strategy_generation_failed') : 'content_strategy_generation_failed'
            );
        }

        return $result;
    }

    private function aysa_adjustments_prepare_content_strategy_payload($project_id, $setup_user_id, $strategy_type) {
        $project_id = intval($project_id);
        $setup_user_id = intval($setup_user_id);
        $strategy_type = sanitize_key((string) $strategy_type);

        return array(
            'project_id' => $project_id,
            'strategy_type' => $strategy_type,
            'raw_context' => $this->aysa_collect_content_strategy_context_bundle($project_id, $setup_user_id),
        );

        $local_setup = $this->aysa_get_local_setup_state_for_user($setup_user_id, $project_id);
        $general = isset($local_setup['general']) && is_array($local_setup['general']) ? $local_setup['general'] : array();
        $seo = isset($local_setup['seo-settings']) && is_array($local_setup['seo-settings']) ? $local_setup['seo-settings'] : array();
        $competition = isset($local_setup['competition']) && is_array($local_setup['competition']) ? $local_setup['competition'] : array();
        $objectives = isset($local_setup['objectives']['objectives']) && is_array($local_setup['objectives']['objectives'])
            ? array_values($local_setup['objectives']['objectives'])
            : array();

        $mappingPayload = $this->aysa_adjustments_extract_keyword_mapping_cluster_payload($project_id);

        $research = $this->aysa_adjustments_get_research_snapshot($project_id, $setup_user_id);
        $modules = isset($research['modules']) && is_array($research['modules']) ? $research['modules'] : array();
        $keyword_rows = array_values(array_filter(array_merge(
            isset($modules['keyword_research']['gsc_rows']) && is_array($modules['keyword_research']['gsc_rows']) ? $modules['keyword_research']['gsc_rows'] : array(),
            isset($modules['keyword_research']['suggestion_rows']) && is_array($modules['keyword_research']['suggestion_rows']) ? $modules['keyword_research']['suggestion_rows'] : array(),
            isset($modules['competitor_gap']['rows']) && is_array($modules['competitor_gap']['rows']) ? $modules['competitor_gap']['rows'] : array()
        ), static function($row) {
            return is_array($row) && trim((string) ($row['keyword'] ?? '')) !== '';
        }));

        $all_keywords = array();
        foreach ($keyword_rows as $row) {
            $keyword = sanitize_text_field((string) ($row['keyword'] ?? ''));
            if ($keyword === '' || in_array($keyword, $all_keywords, true)) {
                continue;
            }
            $all_keywords[] = $keyword;
            if (count($all_keywords) >= 40) {
                break;
            }
        }

        if (is_array($mappingPayload) && !empty($mappingPayload['cluster_groups']) && is_array($mappingPayload['cluster_groups'])) {
            $preferred_keywords = array();
            foreach ($mappingPayload['cluster_groups'] as $cluster_group) {
                if (!is_array($cluster_group)) {
                    continue;
                }
                $selection_state = sanitize_key((string) ($cluster_group['selection_state'] ?? 'review'));
                if ($selection_state === 'excluded') {
                    continue;
                }
                $business_fit_score = (float) ($cluster_group['business_fit_score'] ?? 0.0);
                if ($business_fit_score < 0.55) {
                    continue;
                }

                $primary = sanitize_text_field((string) ($cluster_group['primary_keyword'] ?? ''));
                if ($primary !== '' && !in_array($primary, $preferred_keywords, true)) {
                    $preferred_keywords[] = $primary;
                }

                foreach ((array) ($cluster_group['target_keywords'] ?? array()) as $keyword) {
                    $keyword = sanitize_text_field((string) $keyword);
                    if ($keyword === '' || in_array($keyword, $preferred_keywords, true)) {
                        continue;
                    }
                    $preferred_keywords[] = $keyword;
                }
            }

            if (!empty($preferred_keywords)) {
                $all_keywords = array_slice($preferred_keywords, 0, 40);
            }
        }

        if (empty($all_keywords)) {
            for ($i = 1; $i <= 5; $i++) {
                $keyword = sanitize_text_field((string) ($competition['keyword' . $i] ?? ''));
                if ($keyword === '' || in_array($keyword, $all_keywords, true)) {
                    continue;
                }
                $all_keywords[] = $keyword;
            }
        }

        global $wpdb;
        $strategy_table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $existing_strategy_keywords_raw = $wpdb->get_col($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT keywords FROM {$strategy_table} WHERE project_id = %d AND strategy_type = %s ORDER BY id DESC LIMIT 80",
            $project_id,
            $strategy_type
        ));
        $existing_strategy_keywords = array();
        foreach ((array) $existing_strategy_keywords_raw as $raw_keywords) {
            foreach ($this->aysa_adjustments_normalize_content_keywords($raw_keywords) as $existing_keyword) {
                if (!in_array($existing_keyword, $existing_strategy_keywords, true)) {
                    $existing_strategy_keywords[] = $existing_keyword;
                }
            }
        }

        $keywords = array();
        foreach ($all_keywords as $keyword) {
            $normalized_keyword = $this->aysa_adjustments_normalize_content_keywords(array($keyword));
            $normalized_keyword = !empty($normalized_keyword) ? $normalized_keyword[0] : '';
            if ($normalized_keyword !== '' && in_array($normalized_keyword, $existing_strategy_keywords, true)) {
                continue;
            }
            $keywords[] = $keyword;
            if (count($keywords) >= 12) {
                break;
            }
        }

        if (count($keywords) < 6) {
            foreach ($all_keywords as $keyword) {
                if (in_array($keyword, $keywords, true)) {
                    continue;
                }
                $keywords[] = $keyword;
                if (count($keywords) >= 12) {
                    break;
                }
            }
        }

        $competitors = $this->aysa_adjustments_extract_competitor_domains_from_setup_state($competition, $project_id, $setup_user_id);
        if (empty($competitors) && !empty($modules['competitor_identification']['rows']) && is_array($modules['competitor_identification']['rows'])) {
            foreach ($modules['competitor_identification']['rows'] as $row) {
                $domain = sanitize_text_field((string) ($row['domain'] ?? ''));
                if ($domain === '' || in_array($domain, $competitors, true)) {
                    continue;
                }
                $competitors[] = $domain;
                if (count($competitors) >= 5) {
                    break;
                }
            }
        }

        $missing_rows = isset($modules['missing_pages']['rows']) && is_array($modules['missing_pages']['rows']) ? $modules['missing_pages']['rows'] : array();
        $primary_keyword = !empty($keywords) ? $keywords[0] : sanitize_text_field((string) ($general['company_name'] ?? ''));
        if (!empty($missing_rows)) {
            $base_keyword_pool = $this->aysa_adjustments_normalize_content_keywords(array_merge(
                $keywords,
                array_filter(array(
                    sanitize_text_field((string) ($general['company_name'] ?? '')),
                    sanitize_text_field((string) ($general['industry'] ?? '')),
                    sanitize_text_field((string) ($seo['targeted_city'] ?? '')),
                    sanitize_text_field((string) ($seo['targeted_country'] ?? '')),
                ))
            ));
            foreach ($missing_rows as $missing_row) {
                $candidate_keyword = sanitize_text_field((string) ($missing_row['keyword'] ?? ''));
                if ($candidate_keyword === '') {
                    continue;
                }
                if ($this->aysa_adjustments_is_relevant_content_strategy_item($candidate_keyword, array($candidate_keyword), $base_keyword_pool)) {
                    $primary_keyword = $candidate_keyword;
                    break;
                }
            }
        }

        $payload = array(
            'brand_name' => sanitize_text_field((string) ($general['brand_name'] ?? $general['company_name'] ?? get_bloginfo('name'))),
            'business_name' => sanitize_text_field((string) ($general['company_name'] ?? get_bloginfo('name'))),
            'website' => esc_url_raw((string) ($general['website'] ?? home_url('/'))),
            'industry' => sanitize_text_field((string) ($general['industry'] ?? 'florist')),
            'city' => sanitize_text_field((string) ($seo['targeted_city'] ?? 'Bragadiru')),
            'country' => sanitize_text_field((string) ($seo['targeted_country'] ?? 'Romania')),
            'language' => sanitize_text_field((string) ($seo['primary_language'] ?? 'Romanian')),
            'primary_keyword' => $primary_keyword,
            'cluster_keywords' => $keywords,
            'target_keywords' => $keywords,
            'seed_keywords' => $keywords,
            'competitors' => $competitors,
            'objectives' => $objectives,
            'current_month' => gmdate('M Y'),
        );

        if (is_array($mappingPayload) && !empty($mappingPayload['keywords'])) {
            $payload['primary_keyword'] = sanitize_text_field((string) ($mappingPayload['primary_keyword'] ?? $payload['primary_keyword']));
            $payload['cluster_keywords'] = array_values(array_filter(array_map('sanitize_text_field', (array) ($mappingPayload['keywords'] ?? array()))));
            $payload['target_keywords'] = $payload['cluster_keywords'];
            $payload['seed_keywords'] = $payload['cluster_keywords'];
            $payload['cluster_groups'] = array_values($mappingPayload['cluster_groups'] ?? array());
            $payload['cluster_count'] = intval($mappingPayload['cluster_count'] ?? count($payload['cluster_groups']));
            $payload['gap_keywords'] = array_values(array_filter(array_map('sanitize_text_field', (array) ($mappingPayload['gap_keywords'] ?? array()))));
            $payload['mapped_keywords'] = array_values(array_filter(array_map('sanitize_text_field', (array) ($mappingPayload['mapped_keywords'] ?? array()))));
            $payload['not_mapped_keywords'] = array_values(array_filter(array_map('sanitize_text_field', (array) ($mappingPayload['not_mapped_keywords'] ?? array()))));
            $payload['transactional_keywords'] = array_values(array_filter(array_map('sanitize_text_field', (array) ($mappingPayload['transactional_keywords'] ?? array()))));
            $payload['informational_keywords'] = array_values(array_filter(array_map('sanitize_text_field', (array) ($mappingPayload['informational_keywords'] ?? array()))));
            $payload['local_keywords'] = array_values(array_filter(array_map('sanitize_text_field', (array) ($mappingPayload['local_keywords'] ?? array()))));

            if (!empty($payload['cluster_groups'][0]) && is_array($payload['cluster_groups'][0])) {
                $payload['cluster_id'] = sanitize_text_field((string) ($payload['cluster_groups'][0]['cluster_id'] ?? ''));
                $payload['cluster_name'] = sanitize_text_field((string) ($payload['cluster_groups'][0]['cluster_name'] ?? ''));
            }

            switch ($strategy_type) {
                case 'intent_layering':
                case 'evergreen_fresh_mix':
                case 'customer_journey':
                case 'authority_building':
                case 'persona_based':
                    if (!empty($payload['transactional_keywords'])) {
                        $payload['target_keywords'] = array_slice(array_values(array_unique(array_merge(
                            $payload['transactional_keywords'],
                            array_slice($payload['informational_keywords'] ?? array(), 0, 8)
                        ))), 0, 12);
                    }
                    break;
                case 'seasonal_event':
                case 'serp_features':
                    if (!empty($payload['informational_keywords'])) {
                        $payload['target_keywords'] = array_slice(array_values(array_unique(array_merge(
                            $payload['informational_keywords'],
                            array_slice($payload['transactional_keywords'] ?? array(), 0, 6)
                        ))), 0, 12);
                    }
                    break;
                case 'local_seo':
                    if (!empty($payload['local_keywords'])) {
                        $payload['target_keywords'] = array_slice(array_values(array_unique(array_merge(
                            $payload['local_keywords'],
                            array_slice($payload['transactional_keywords'] ?? array(), 0, 6)
                        ))), 0, 12);
                    } elseif (!empty($payload['transactional_keywords'])) {
                        $payload['target_keywords'] = array_slice($payload['transactional_keywords'], 0, 12);
                    }
                    break;
            }
        }

        if ($strategy_type === 'persona_based') {
            $personaClusterGroups = $this->aysa_adjustments_prepare_persona_cluster_groups((array) ($payload['cluster_groups'] ?? array()));
            if (!empty($personaClusterGroups)) {
                $payload['cluster_groups'] = $personaClusterGroups;
                $payload['cluster_count'] = count($personaClusterGroups);
                $payload['cluster_id'] = sanitize_text_field((string) ($personaClusterGroups[0]['cluster_id'] ?? ''));
                $payload['cluster_name'] = sanitize_text_field((string) ($personaClusterGroups[0]['cluster_name'] ?? ''));

                $personaKeywords = array();
                foreach ($personaClusterGroups as $personaGroup) {
                    foreach ((array) ($personaGroup['target_keywords'] ?? array()) as $personaKeyword) {
                        $personaKeyword = sanitize_text_field((string) $personaKeyword);
                        if ($personaKeyword !== '' && !in_array($personaKeyword, $personaKeywords, true)) {
                            $personaKeywords[] = $personaKeyword;
                        }
                    }
                }
                if (!empty($personaKeywords)) {
                    $payload['target_keywords'] = array_slice($personaKeywords, 0, 18);
                    $payload['cluster_keywords'] = $payload['target_keywords'];
                }
            }

            $payload['persona_context'] = array(
                'business_type' => sanitize_text_field((string) ($payload['industry'] ?? '')),
                'primary_objective' => !empty($objectives) ? sanitize_text_field((string) $objectives[0]) : 'sales',
                'website_type' => sanitize_text_field((string) ($general['website_type'] ?? '')),
                'company_info' => sanitize_text_field((string) ($general['company_info'] ?? '')),
                'city' => sanitize_text_field((string) ($payload['city'] ?? '')),
                'country' => sanitize_text_field((string) ($payload['country'] ?? '')),
            );
            $payload['persona_scenarios'] = $this->aysa_adjustments_build_persona_scenarios((array) ($payload['cluster_groups'] ?? array()), $payload);

            $personaLabels = array_values(array_unique(array_filter(array_map(static function ($row) {
                return is_array($row) ? trim((string) ($row['persona'] ?? '')) : '';
            }, (array) $payload['persona_scenarios']))));

            $payload['target_persona'] = !empty($personaLabels)
                ? sanitize_text_field(implode(' | ', array_slice($personaLabels, 0, 3)))
                : (!empty($objectives)
                    ? sanitize_text_field(implode(', ', array_slice($objectives, 0, 3)))
                    : sanitize_text_field((string) ($general['company_info'] ?? '')));
        }

        if ($strategy_type === 'competitor_derived_titles') {
            $competitorClusterGroups = $this->aysa_adjustments_prepare_competitor_cluster_groups((array) ($payload['cluster_groups'] ?? array()));
            if (!empty($competitorClusterGroups)) {
                $payload['cluster_groups'] = $competitorClusterGroups;
                $payload['cluster_count'] = count($competitorClusterGroups);
                $payload['cluster_id'] = sanitize_text_field((string) ($competitorClusterGroups[0]['cluster_id'] ?? ''));
                $payload['cluster_name'] = sanitize_text_field((string) ($competitorClusterGroups[0]['cluster_name'] ?? ''));

                $competitorKeywords = array();
                foreach ($competitorClusterGroups as $competitorGroup) {
                    foreach ((array) ($competitorGroup['target_keywords'] ?? array()) as $competitorKeyword) {
                        $competitorKeyword = sanitize_text_field((string) $competitorKeyword);
                        if ($competitorKeyword !== '' && !in_array($competitorKeyword, $competitorKeywords, true)) {
                            $competitorKeywords[] = $competitorKeyword;
                        }
                    }
                }
                if (!empty($payload['gap_keywords'])) {
                    $competitorKeywords = array_values(array_unique(array_merge(
                        array_slice((array) $payload['gap_keywords'], 0, 18),
                        $competitorKeywords
                    )));
                }
                if (!empty($competitorKeywords)) {
                    $payload['target_keywords'] = array_slice($competitorKeywords, 0, 18);
                    $payload['cluster_keywords'] = $payload['target_keywords'];
                }
            }

            $payload['competitor_domains'] = array_values(array_filter(array_map('sanitize_text_field', (array) $competitors)));
            $freshCompetitorDiscovery = $this->aysa_adjustments_get_competitor_fresh_content($project_id, (array) ($payload['competitor_domains'] ?? array()));
            $payload['competitor_fresh_content'] = $freshCompetitorDiscovery;
            $payload['competitor_title_signals'] = $this->aysa_adjustments_build_competitor_title_signals(
                $project_id,
                (array) ($payload['cluster_groups'] ?? array()),
                $payload,
                (array) ($payload['competitor_domains'] ?? array())
            );

            $signalTitles = array();
            $freshTitles = array();
            foreach ((array) ($freshCompetitorDiscovery['rows'] ?? array()) as $freshRow) {
                if (!is_array($freshRow)) {
                    continue;
                }
                $freshTitle = sanitize_text_field((string) ($freshRow['title'] ?? ''));
                if ($freshTitle !== '' && !in_array($freshTitle, $freshTitles, true)) {
                    $freshTitles[] = $freshTitle;
                }
            }
            foreach ((array) $payload['competitor_title_signals'] as $signalRow) {
                if (!is_array($signalRow)) {
                    continue;
                }
                $signalTitle = sanitize_text_field((string) ($signalRow['competitor_title'] ?? ''));
                if ($signalTitle !== '' && !in_array($signalTitle, $signalTitles, true)) {
                    $signalTitles[] = $signalTitle;
                }
            }
            $payload['competitor_titles'] = array_slice(array_values(array_unique(array_merge($freshTitles, $signalTitles))), 0, 18);
            if (empty($payload['competitor_titles']) && !empty($signalTitles)) {
                $payload['competitor_titles'] = array_slice($signalTitles, 0, 18);
            }

            $payload['competitor_analysis_context'] = $this->aysa_adjustments_build_competitor_analysis_context(
                (array) ($payload['cluster_groups'] ?? array()),
                $payload,
                (array) ($payload['competitor_title_signals'] ?? array()),
                $freshCompetitorDiscovery
            );
        }

        if ($strategy_type === 'local_seo') {
            $localClusterGroups = $this->aysa_adjustments_prepare_local_seo_cluster_groups((array) ($payload['cluster_groups'] ?? array()));
            if (!empty($localClusterGroups)) {
                $payload['cluster_groups'] = $localClusterGroups;
                $payload['cluster_count'] = count($localClusterGroups);
                $payload['cluster_id'] = sanitize_text_field((string) ($localClusterGroups[0]['cluster_id'] ?? ''));
                $payload['cluster_name'] = sanitize_text_field((string) ($localClusterGroups[0]['cluster_name'] ?? ''));

                $localKeywords = array();
                foreach ($localClusterGroups as $localGroup) {
                    foreach ((array) ($localGroup['target_keywords'] ?? array()) as $localKeyword) {
                        $localKeyword = sanitize_text_field((string) $localKeyword);
                        if ($localKeyword !== '' && !in_array($localKeyword, $localKeywords, true)) {
                            $localKeywords[] = $localKeyword;
                        }
                    }
                }
                if (!empty($localKeywords)) {
                    $payload['target_keywords'] = array_slice($localKeywords, 0, 18);
                    $payload['cluster_keywords'] = $payload['target_keywords'];
                }
            }

            $payload['neighborhoods'] = array_values(array_filter(array(
                sanitize_text_field((string) ($seo['targeted_region'] ?? '')),
                sanitize_text_field((string) ($seo['targeted_city'] ?? '')),
            )));
            $payload['local_context'] = $this->aysa_adjustments_build_local_context((array) ($payload['cluster_groups'] ?? array()), array_merge($payload, array(
                'target_region' => sanitize_text_field((string) ($seo['targeted_region'] ?? '')),
            )));
        }

        if ($strategy_type === 'authority_building') {
            $authorityClusterGroups = $this->aysa_adjustments_prepare_authority_cluster_groups((array) ($payload['cluster_groups'] ?? array()));
            if (!empty($authorityClusterGroups)) {
                $payload['cluster_groups'] = $authorityClusterGroups;
                $payload['cluster_count'] = count($authorityClusterGroups);
                $payload['cluster_id'] = sanitize_text_field((string) ($authorityClusterGroups[0]['cluster_id'] ?? ''));
                $payload['cluster_name'] = sanitize_text_field((string) ($authorityClusterGroups[0]['cluster_name'] ?? ''));

                $authorityKeywords = array();
                foreach ($authorityClusterGroups as $authorityGroup) {
                    foreach ((array) ($authorityGroup['target_keywords'] ?? array()) as $authorityKeyword) {
                        $authorityKeyword = sanitize_text_field((string) $authorityKeyword);
                        if ($authorityKeyword !== '' && !in_array($authorityKeyword, $authorityKeywords, true)) {
                            $authorityKeywords[] = $authorityKeyword;
                        }
                    }
                }
                if (!empty($authorityKeywords)) {
                    $payload['target_keywords'] = array_slice($authorityKeywords, 0, 18);
                    $payload['cluster_keywords'] = $payload['target_keywords'];
                }
            }

            $payload['authority_context'] = $this->aysa_adjustments_build_authority_context((array) ($payload['cluster_groups'] ?? array()), $payload);
        }

        if ($strategy_type === 'customer_journey') {
            $payload['journey_context'] = $this->aysa_adjustments_build_customer_journey_context((array) ($payload['cluster_groups'] ?? array()), $payload);
        }

        if ($strategy_type === 'seasonal_event') {
            $payload['current_date'] = gmdate('Y-m-d');
            $payload['industry_context'] = array(
                'current_month' => sanitize_text_field((string) ($payload['current_month'] ?? '')),
                'business_name' => sanitize_text_field((string) ($payload['business_name'] ?? '')),
                'industry' => sanitize_text_field((string) ($payload['industry'] ?? '')),
                'company_info' => sanitize_text_field((string) ($payload['company_info'] ?? '')),
                'city' => sanitize_text_field((string) ($payload['city'] ?? '')),
                'country' => sanitize_text_field((string) ($payload['country'] ?? '')),
                'seasonality_hint' => sanitize_text_field((string) ($payload['city'] ?? '')) . ' ' . sanitize_text_field((string) ($payload['current_month'] ?? '')),
                'cluster_context' => $this->aysa_adjustments_build_evergreen_fresh_context((array) ($payload['cluster_groups'] ?? array()), $payload),
                'known_events' => array_values(array_filter(array(
                    '8 Martie',
                    'Paste',
                    'Florii',
                    '1 Martie',
                    'Black Friday',
                    'Craciun',
                    'Valentine\'s Day',
                ))),
            );
        }

        if ($strategy_type === 'evergreen_fresh_mix') {
            $payload['industry_context'] = array(
                'current_month' => sanitize_text_field((string) ($payload['current_month'] ?? '')),
                'business_name' => sanitize_text_field((string) ($payload['business_name'] ?? '')),
                'industry' => sanitize_text_field((string) ($payload['industry'] ?? '')),
                'company_info' => sanitize_text_field((string) ($payload['company_info'] ?? '')),
                'city' => sanitize_text_field((string) ($payload['city'] ?? '')),
                'seasonality_hint' => sanitize_text_field((string) ($payload['city'] ?? '')) . ' ' . sanitize_text_field((string) ($payload['current_month'] ?? '')),
                'cluster_context' => $this->aysa_adjustments_build_evergreen_fresh_context((array) ($payload['cluster_groups'] ?? array()), $payload),
            );
        }

        $table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        $allowed_keyword_pool = $this->aysa_adjustments_build_allowed_content_keyword_pool($payload);
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $existing_rows = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT title, keywords, strategy_type FROM {$table}
             WHERE project_id = %d
               AND COALESCE(automation_status, '') <> 'deleted'
             ORDER BY id DESC LIMIT 80",
            $project_id
        ), ARRAY_A);
        $existing_titles = array();
        $existing_keywords = array();
        foreach ((array) $existing_rows as $row) {
            $title = sanitize_text_field((string) ($row['title'] ?? ''));
            $row_keywords = $this->aysa_adjustments_normalize_content_keywords($row['keywords'] ?? array());
            if (!$this->aysa_adjustments_is_relevant_content_strategy_item($title, $row_keywords, $allowed_keyword_pool)) {
                continue;
            }
            if ($title !== '' && !in_array($title, $existing_titles, true)) {
                $existing_titles[] = $title;
            }
            foreach ($row_keywords as $keyword) {
                if (!in_array($keyword, $existing_keywords, true)) {
                    $existing_keywords[] = $keyword;
                }
            }
        }
        if (!empty($existing_titles)) {
            $payload['recent_existing_titles'] = array_slice($existing_titles, 0, 20);
        }
        if (!empty($existing_keywords)) {
            $payload['existing_target_keywords'] = array_slice($existing_keywords, 0, 40);
        }

        return $payload;
    }

    private function aysa_adjustments_save_content_strategy_items($project_id, $strategy_type, $items, $fallback_payload = array()) {
        global $wpdb;

        $project_id = intval($project_id);
        $strategy_type = sanitize_text_field((string) $strategy_type);
        $items = is_array($items) ? $items : array();
        $fallback_payload = is_array($fallback_payload) ? $fallback_payload : array();

        $table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if ($table_exists !== $table) {
            if (!class_exists('Aysa_Content_Strategy_Manager')) {
                require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-content-strategy-manager.php';
            }
            if (class_exists('Aysa_Content_Strategy_Manager')) {
                $manager = new Aysa_Content_Strategy_Manager();
                $manager->create_table();
            }
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
            if ($table_exists !== $table) {
                return array('saved' => 0, 'existing' => 0, 'errors' => count($items));
            }
        }

        $saved = 0;
        $existing = 0;
        $errors = 0;
        $saved_items = array();
        $existing_items = array();
        $plan_batch = 'ai-' . gmdate('YmdHis') . '-' . wp_generate_password(6, false, false);
        $default_keywords = isset($fallback_payload['target_keywords']) && is_array($fallback_payload['target_keywords'])
            ? array_values($fallback_payload['target_keywords'])
            : array();
        $allowed_keyword_pool = $this->aysa_adjustments_build_allowed_content_keyword_pool($fallback_payload);
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $existing_titles = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT title, keywords FROM {$table} WHERE project_id = %d AND strategy_type = %s",
            $project_id,
            $strategy_type
        ), ARRAY_A);
        $topic_pool = array();
        $keyword_pool = array();
        if (is_array($existing_titles)) {
            foreach ($existing_titles as $existing_row) {
                $existing_title = sanitize_text_field((string) ($existing_row['title'] ?? ''));
                if ($existing_title !== '') {
                    $topic_pool[] = $existing_title;
                }
                $existing_keywords = $this->aysa_adjustments_normalize_content_keywords($existing_row['keywords'] ?? array());
                if (!empty($existing_keywords)) {
                    $keyword_pool[] = $existing_keywords;
                }
            }
        }

        foreach ($items as $item) {
            if (!is_array($item)) {
                continue;
            }
            $title = sanitize_text_field((string) ($item['title'] ?? ''));
            if ($title === '') {
                continue;
            }
            if ($this->aysa_adjustments_is_duplicate_content_topic($title, $topic_pool)) {
                $existing++;
                $existing_items[] = $item;
                continue;
            }

            $keywords = isset($item['target_keywords']) && is_array($item['target_keywords'])
                ? array_values(array_filter(array_map('sanitize_text_field', $item['target_keywords'])))
                : $default_keywords;
            $normalized_keywords = $this->aysa_adjustments_normalize_content_keywords($keywords);
            $intent = sanitize_text_field((string) ($item['intent'] ?? 'I'));
            $associated_page = sanitize_text_field((string) ($item['associated_page'] ?? $item['associatedPage'] ?? ''));
            $strategy_meta = array();

            if ($strategy_type === 'competitor_derived_titles') {
                $strategy_meta = array(
                    'competitor_domain' => sanitize_text_field((string) ($item['competitor_domain'] ?? '')),
                    'competitor_title' => sanitize_text_field((string) ($item['competitor_title'] ?? '')),
                    'competitor_url' => esc_url_raw((string) ($item['competitor_url'] ?? '')),
                    'strategy' => sanitize_key((string) ($item['strategy'] ?? '')),
                    'improvement_type' => sanitize_key((string) ($item['improvement_type'] ?? '')),
                    'why_better' => sanitize_text_field((string) ($item['why_better'] ?? '')),
                );
            }

            if ($strategy_type === 'competitor_derived_titles') {
                if (!empty($allowed_keyword_pool) && !empty($normalized_keywords)) {
                    $keyword_relevance_score = $this->aysa_adjustments_content_keywords_overlap_score($normalized_keywords, $allowed_keyword_pool);
                    if ($keyword_relevance_score < 0.2) {
                        $errors++;
                        continue;
                    }
                } elseif (!$this->aysa_adjustments_is_relevant_content_strategy_item($title, $normalized_keywords, $allowed_keyword_pool)) {
                    $errors++;
                    continue;
                }
            } elseif (!$this->aysa_adjustments_is_relevant_content_strategy_item($title, $normalized_keywords, $allowed_keyword_pool)) {
                $errors++;
                continue;
            }

            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $exists = $wpdb->get_var($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT id FROM {$table} WHERE project_id = %d AND title = %s AND strategy_type = %s AND COALESCE(associated_page, '') = %s LIMIT 1",
                $project_id,
                $title,
                $strategy_type,
                $associated_page
            ));
            if (!empty($exists)) {
                $existing++;
                $existing_items[] = $item;
                continue;
            }

            if ($this->aysa_adjustments_is_duplicate_content_keywords($normalized_keywords, $keyword_pool)) {
                $existing++;
                $existing_items[] = $item;
                continue;
            }

            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $inserted = $wpdb->insert(
                $table,
                array(
                    'project_id' => $project_id,
                    'title' => $title,
                    'keywords' => !empty($keywords) ? wp_json_encode($keywords) : '',
                    'intent' => $intent,
                    'associated_page' => $associated_page,
                    'strategy_type' => $strategy_type,
                    'strategy_meta' => !empty($strategy_meta) ? wp_json_encode($strategy_meta) : null,
                    'recommended_page_type' => null,
                    'content_type' => 'post',
                    'execution_stage' => 'planned',
                    'status' => 'proposed',
                    'automation_status' => null,
                    'automation_attempts' => 0,
                    'scheduled_for' => null,
                    'generated_at' => null,
                    'plan_batch' => $plan_batch,
                    'last_error' => null,
                    'automation_updated_at' => current_time('mysql'),
                )
            );

            if ($inserted === false) {
                $errors++;
                continue;
            }
            $saved++;
            $saved_items[] = $item;
            $topic_pool[] = $title;
            if (!empty($normalized_keywords)) {
                $keyword_pool[] = $normalized_keywords;
            }
        }

        if ($saved > 0) {
            delete_option('aysa_content_strategy_hard_reset_' . $project_id);
        }

        return array(
            'saved' => $saved,
            'existing' => $existing,
            'errors' => $errors,
            'plan_batch' => $saved > 0 ? $plan_batch : '',
            'saved_items' => $saved_items,
            'existing_items' => $existing_items,
        );
    }

    private function aysa_adjustments_extract_competitor_domains_from_setup_state($competition, $project_id = 0, $setup_user_id = 0) {
        $competition = is_array($competition) ? $competition : array();
        $domains = array();
        $seen = array();

        $push_domain = static function($value) use (&$domains, &$seen) {
            $value = trim((string) $value);
            if ($value === '') {
                return;
            }

            $value = preg_replace('#^https?://#i', '', $value);
            $value = preg_replace('#^www\.#i', '', $value);
            $value = trim((string) $value, "/ \t\n\r\0\x0B");
            $value = strtolower($value);

            if ($value === '' || strpos($value, '.') === false) {
                return;
            }

            if (isset($seen[$value])) {
                return;
            }

            $seen[$value] = true;
            $domains[] = $value;
        };

        foreach ($competition as $key => $value) {
            $key = (string) $key;

            if (preg_match('/^competitor\d+$/', $key) || preg_match('/^competitor\d+_(domain|url)$/', $key)) {
                $push_domain($value);
                continue;
            }

            if ($key === 'competitors' && is_array($value)) {
                foreach ($value as $competitor_row) {
                    if (is_array($competitor_row)) {
                        $push_domain($competitor_row['domain'] ?? '');
                        $push_domain($competitor_row['url'] ?? '');
                        $push_domain($competitor_row['site'] ?? '');
                    } else {
                        $push_domain($competitor_row);
                    }
                }
            }
        }

        if (!empty($domains)) {
            return $domains;
        }

        global $wpdb;
        $project_id = intval($project_id);
        $setup_user_id = intval($setup_user_id);

        $fallback_options = array();
        if ($project_id > 0) {
            $fallback_options[] = get_option('aysa_wizard_competition_project_' . $project_id, array());
        }
        if ($setup_user_id > 0) {
            $fallback_options[] = get_option('aysa_wizard_competition_' . $setup_user_id, array());
        }

        foreach ($fallback_options as $fallback_option) {
            if (!is_array($fallback_option) || empty($fallback_option)) {
                continue;
            }
            foreach ($fallback_option as $key => $value) {
                $key = (string) $key;
                if (preg_match('/^competitor\d+$/', $key) || preg_match('/^competitor\d+_(domain|url)$/', $key)) {
                    $push_domain($value);
                }
            }
            if (!empty($domains)) {
                return $domains;
            }
        }

        if (isset($wpdb) && $wpdb instanceof wpdb) {
            $like = $wpdb->esc_like('aysa_wizard_competition') . '%';
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $rows = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT option_value FROM {$wpdb->options} WHERE option_name LIKE %s ORDER BY option_id DESC LIMIT 50",
                    $like
                ),
                ARRAY_A
            );
            if (is_array($rows)) {
                foreach ($rows as $row) {
                    $raw_option = isset($row['option_value']) ? maybe_unserialize($row['option_value']) : array();
                    if (!is_array($raw_option) || empty($raw_option)) {
                        continue;
                    }
                    foreach ($raw_option as $key => $value) {
                        $key = (string) $key;
                        if (preg_match('/^competitor\d+$/', $key) || preg_match('/^competitor\d+_(domain|url)$/', $key)) {
                            $push_domain($value);
                        }
                    }
                    if (!empty($domains)) {
                        return $domains;
                    }
                }
            }
        }

        return array();
    }

    private function aysa_adjustments_build_topic_signature($title) {
        $title = strtolower(trim(remove_accents((string) $title)));
        if ($title === '') {
            return '';
        }

        $title = preg_replace('/[^a-z0-9\\s]+/u', ' ', $title);
        $parts = preg_split('/\\s+/', $title);
        if (!is_array($parts)) {
            return '';
        }

        $stopwords = array(
            'si','sau','de','la','in','din','pe','cu','pentru','cele','cel','cea','cei','un','o','a','ale','al',
            'the','and','for','with','from','into','your','how','top','guide','ghid','complet','best','celemai',
            '2024','2025','2026'
        );
        $stop = array_fill_keys($stopwords, true);
        $tokens = array();

        foreach ($parts as $p) {
            $p = trim((string) $p);
            if ($p === '' || isset($stop[$p])) {
                continue;
            }
            if (strlen($p) < 3) {
                continue;
            }
            $tokens[$p] = true;
        }

        if (empty($tokens)) {
            return '';
        }

        $keys = array_keys($tokens);
        sort($keys);
        return implode(' ', $keys);
    }

    private function aysa_adjustments_topic_similarity_score($a, $b) {
        $sa = $this->aysa_adjustments_build_topic_signature($a);
        $sb = $this->aysa_adjustments_build_topic_signature($b);
        if ($sa === '' || $sb === '') {
            return 0.0;
        }
        if ($sa === $sb) {
            return 1.0;
        }

        $ta = array_fill_keys(explode(' ', $sa), true);
        $tb = array_fill_keys(explode(' ', $sb), true);
        $inter = array_intersect_key($ta, $tb);
        $union = $ta + $tb;
        return count($union) > 0 ? (count($inter) / count($union)) : 0.0;
    }

    private function aysa_adjustments_is_duplicate_content_topic($title, $pool) {
        $title_clean = trim((string) $title);
        if ($title_clean === '') {
            return false;
        }

        foreach ((array) $pool as $existing) {
            $existing_clean = trim((string) $existing);
            if ($existing_clean === '') {
                continue;
            }
            if (strcasecmp($title_clean, $existing_clean) === 0) {
                return true;
            }
            $score = $this->aysa_adjustments_topic_similarity_score($title_clean, $existing_clean);
            if ($score >= 0.75) {
                return true;
            }
        }

        return false;
    }

    private function aysa_adjustments_normalize_content_keywords($keywords) {
        if (is_string($keywords)) {
            $decoded = json_decode($keywords, true);
            if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                $keywords = $decoded;
            } else {
                $keywords = array($keywords);
            }
        }

        $normalized = array();
        foreach ((array) $keywords as $keyword) {
            $keyword = strtolower(trim(remove_accents((string) $keyword)));
            if ($keyword === '') {
                continue;
            }
            $keyword = preg_replace('/[^a-z0-9\\s]+/u', ' ', $keyword);
            $keyword = preg_replace('/\\s+/u', ' ', $keyword);
            $keyword = trim((string) $keyword);
            if ($keyword === '') {
                continue;
            }
            $normalized[$keyword] = true;
        }

        return array_keys($normalized);
    }

    private function aysa_adjustments_content_keywords_overlap_score($keywords, $existing_keywords) {
        $a = array_fill_keys((array) $keywords, true);
        $b = array_fill_keys((array) $existing_keywords, true);
        if (empty($a) || empty($b)) {
            return 0.0;
        }

        $intersection = array_intersect_key($a, $b);
        $base = min(count($a), count($b));
        if ($base <= 0) {
            return 0.0;
        }

        return count($intersection) / $base;
    }

    private function aysa_adjustments_is_duplicate_content_keywords($keywords, $pool) {
        $keywords = array_values(array_filter((array) $keywords));
        if (empty($keywords)) {
            return false;
        }

        foreach ((array) $pool as $existing_keywords) {
            $existing_keywords = array_values(array_filter((array) $existing_keywords));
            if (empty($existing_keywords)) {
                continue;
            }

            $score = $this->aysa_adjustments_content_keywords_overlap_score($keywords, $existing_keywords);
            if ($score >= 0.5) {
                return true;
            }
        }

        return false;
    }

    private function aysa_adjustments_build_allowed_content_keyword_pool($payload) {
        $payload = is_array($payload) ? $payload : array();
        $raw = array();
        $keys = array('target_keywords', 'cluster_keywords', 'seed_keywords');
        foreach ($keys as $key) {
            if (!empty($payload[$key]) && is_array($payload[$key])) {
                $raw = array_merge($raw, $payload[$key]);
            }
        }

        foreach (array('brand_name', 'business_name', 'industry', 'city', 'country', 'primary_keyword') as $key) {
            if (!empty($payload[$key])) {
                $raw[] = (string) $payload[$key];
            }
        }

        return $this->aysa_adjustments_normalize_content_keywords($raw);
    }

    private function aysa_adjustments_is_relevant_content_strategy_item($title, $keywords, $allowed_pool) {
        $allowed_pool = array_values(array_filter((array) $allowed_pool));
        if (empty($allowed_pool)) {
            return true;
        }

        $title_tokens = $this->aysa_adjustments_normalize_content_keywords(array($title));
        $keywords = array_values(array_filter((array) $keywords));
        $combined = array_values(array_unique(array_merge($title_tokens, $keywords)));
        if (empty($combined)) {
            return false;
        }

        $score = $this->aysa_adjustments_content_keywords_overlap_score($combined, $allowed_pool);
        if ($score >= 0.5) {
            return true;
        }

        foreach ($combined as $token) {
            $token = (string) $token;
            if (strlen($token) < 4) {
                continue;
            }
            if (in_array($token, $allowed_pool, true)) {
                return true;
            }
        }

        return false;
    }

    private function aysa_adjustments_delete_content_strategy_batch($project_id, $plan_batch) {
        global $wpdb;

        $project_id = intval($project_id);
        $plan_batch = sanitize_text_field((string) $plan_batch);
        if ($project_id <= 0 || $plan_batch === '') {
            return array('deleted' => 0);
        }

        $table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $deleted = $wpdb->query($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "DELETE FROM {$table} WHERE project_id = %d AND plan_batch = %s",
            $project_id,
            $plan_batch
        ));

        return array('deleted' => max(0, intval($deleted)));
    }

    public function handle_missing_pages_send_to_plan() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_content_strategy_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $raw_items = wp_unslash($_POST['items'] ?? array());
        if (is_string($raw_items)) {
            $decoded = json_decode(wp_unslash($raw_items), true);
            $raw_items = is_array($decoded) ? $decoded : array();
        }
        if (!is_array($raw_items) || empty($raw_items)) {
            $single_keyword = sanitize_text_field((string) wp_unslash($_POST['keyword'] ?? ''));
            if ($single_keyword !== '') {
                $raw_items = array(array(
                    'keyword' => $single_keyword,
                    'intent' => sanitize_text_field((string) wp_unslash($_POST['intent'] ?? 'N/A')),
                    'source' => sanitize_text_field((string) wp_unslash($_POST['source'] ?? 'missing_pages')),
                    'recommended_page_type' => sanitize_text_field((string) wp_unslash($_POST['recommended_page_type'] ?? 'Landing Page')),
                    'recommendation_reason' => sanitize_text_field((string) wp_unslash($_POST['recommendation_reason'] ?? '')),
                ));
            }
        }
        if (empty($raw_items)) {
            wp_send_json_error('Missing keyword payload');
            return;
        }

        global $wpdb;
        $table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if ($table_exists !== $table) {
            if (!class_exists('Aysa_Content_Strategy_Manager')) {
                require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-content-strategy-manager.php';
            }
            if (class_exists('Aysa_Content_Strategy_Manager')) {
                $manager = new Aysa_Content_Strategy_Manager();
                $manager->create_table();
            }
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
            if ($table_exists !== $table) {
                wp_send_json_error('Content strategy table unavailable');
                return;
            }
        }

        $result = $this->aysa_missing_pages_send_items_to_plan($project_id, $raw_items);

        wp_send_json_success(array(
            'added' => intval($result['added'] ?? 0),
            'existing' => intval($result['existing'] ?? 0),
            'errors' => intval($result['errors'] ?? 0),
            'total' => intval($result['total'] ?? 0),
        ));
    }

    private function aysa_missing_pages_send_items_to_plan($project_id, $raw_items) {
        global $wpdb;

        $project_id = intval($project_id);
        $raw_items = is_array($raw_items) ? $raw_items : array();
        $table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if ($table_exists !== $table) {
            if (!class_exists('Aysa_Content_Strategy_Manager')) {
                require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-content-strategy-manager.php';
            }
            if (class_exists('Aysa_Content_Strategy_Manager')) {
                $manager = new Aysa_Content_Strategy_Manager();
                $manager->create_table();
            }
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
            if ($table_exists !== $table) {
                return array('added' => 0, 'existing' => 0, 'errors' => count($raw_items), 'total' => count($raw_items));
            }
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $existing_titles = $wpdb->get_col($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT title
" .
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
            "             FROM {$table}
             WHERE project_id = %d
               AND strategy_type = 'missing_pages'
               AND COALESCE(automation_status, '') <> 'deleted'",
            $project_id
        ));

        $result = $this->aysa_broker_request('POST', '/v1/content-strategy/missing-pages-send-to-plan', array(
            'project_id' => $project_id,
            'items' => array_values($raw_items),
            'existing_titles' => is_array($existing_titles) ? array_values($existing_titles) : array(),
        ), 60);
        if (is_wp_error($result) || !is_array($result) || empty($result['ok'])) {
            return array(
                'added' => 0,
                'existing' => 0,
                'errors' => count($raw_items),
                'total' => count($raw_items),
            );
        }

        return $this->aysa_missing_pages_insert_plan_rows(
            $project_id,
            isset($result['rows']) && is_array($result['rows']) ? $result['rows'] : array(),
            intval($result['existing'] ?? 0),
            intval($result['errors'] ?? 0)
        );
    }

    private function aysa_missing_pages_insert_plan_rows($project_id, $rows_to_insert, $existing = 0, $errors = 0) {
        global $wpdb;

        $project_id = intval($project_id);
        $existing = intval($existing);
        $errors = intval($errors);
        $rows_to_insert = is_array($rows_to_insert) ? $rows_to_insert : array();
        $table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        $added = 0;

        foreach ($rows_to_insert as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keywords_payload = wp_json_encode(array_values(array_filter(array_map('strval', (array) ($row['keywords'] ?? array())))));
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $inserted = $wpdb->insert(
                $table,
                array(
                    'project_id' => $project_id,
                    'title' => sanitize_text_field((string) ($row['title'] ?? '')),
                    'keywords' => $keywords_payload,
                    'intent' => sanitize_text_field((string) ($row['intent'] ?? 'N/A')),
                    'associated_page' => sanitize_text_field((string) ($row['associated_page'] ?? '')),
                    'strategy_type' => sanitize_text_field((string) ($row['strategy_type'] ?? 'missing_pages')),
                    'recommended_page_type' => sanitize_text_field((string) ($row['recommended_page_type'] ?? 'Landing Page')),
                    'content_type' => sanitize_key((string) ($row['content_type'] ?? 'landing_page')),
                    'execution_stage' => sanitize_key((string) ($row['execution_stage'] ?? 'planned')),
                    'status' => sanitize_key((string) ($row['status'] ?? 'proposed')),
                    'automation_status' => sanitize_text_field((string) ($row['automation_status'] ?? '')),
                    'automation_attempts' => intval($row['automation_attempts'] ?? 0),
                    'scheduled_for' => !empty($row['scheduled_for']) ? sanitize_text_field((string) $row['scheduled_for']) : null,
                    'generated_at' => !empty($row['generated_at']) ? sanitize_text_field((string) $row['generated_at']) : null,
                    'plan_batch' => sanitize_text_field((string) ($row['plan_batch'] ?? 'missing_pages')),
                    'last_error' => sanitize_text_field((string) ($row['last_error'] ?? '')),
                    'automation_updated_at' => current_time('mysql'),
                ),
                array('%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s', '%s', '%s', '%s', '%s', '%s')
            );

            if ($inserted === false) {
                $errors++;
                continue;
            }
            $added++;
        }

        return array(
            'added' => $added,
            'existing' => $existing,
            'errors' => $errors,
            'total' => $added + $existing + $errors,
        );
    }

    private function aysa_build_missing_page_meta_prompt($keyword, $page_type, $intent, $context = array()) {
        $brand_name = trim((string) ($context['brand_name'] ?? ''));
        $city = trim((string) ($context['targeted_city'] ?? ''));
        $country = trim((string) ($context['targeted_country'] ?? 'Romania'));
        $language = trim((string) ($context['primary_language'] ?? 'Romanian'));
        $audience = trim((string) ($context['target_audience'] ?? ''));
        $website_type = trim((string) ($context['website_type'] ?? ''));

        $lines = array(
            'You are an SEO metadata expert.',
            'Generate metadata for ONE page based on this input:',
            '- keyword: "' . $keyword . '"',
            '- page_type: "' . $page_type . '" (allowed: category, product, landing_page)',
            '- intent: "' . $intent . '"',
            '- language: "' . $language . '"',
            '- city: "' . $city . '"',
            '- country: "' . $country . '"',
            '- brand_name: "' . $brand_name . '"',
            '- target_audience: "' . $audience . '"',
            '- website_type: "' . $website_type . '"',
            '',
            'Strict rules:',
            '1) title length: 50-60 chars, include keyword naturally, maximize CTR.',
            '2) h1 length: 20-70 chars, include keyword exactly once.',
            '3) meta_description length: 140-160 chars, include keyword naturally, include one CTA.',
            '4) no keyword stuffing, no quotes, no emoji, no ALL CAPS.',
            '5) if city is present, include it naturally in either title or meta description.',
            '6) keep wording human and commercial-ready.',
            '',
            'Return only valid JSON object:',
            '{',
            '  "keyword": "string",',
            '  "page_type": "category|product|landing_page",',
            '  "title": "string",',
            '  "h1": "string",',
            '  "meta_description": "string"',
            '}',
        );

        return implode("\n", $lines);
    }

    private function aysa_missing_page_meta_table() {
        global $wpdb;
        return $wpdb->prefix . 'aysa_missing_page_meta';
    }

    private function aysa_missing_page_meta_maybe_create_table() {
        global $wpdb;
        $table = esc_sql($this->aysa_missing_page_meta_table());
        $charset_collate = $wpdb->get_charset_collate();
        $sql = "CREATE TABLE {$table} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            project_id mediumint(9) NOT NULL,
            strategy_id mediumint(9) DEFAULT NULL,
            keyword varchar(191) NOT NULL,
            page_type varchar(32) NOT NULL,
            intent varchar(8) DEFAULT 'N/A',
            seo_title varchar(255) NOT NULL,
            h1 text,
            meta_description text,
            model varchar(128) DEFAULT '',
            warning varchar(128) DEFAULT '',
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            UNIQUE KEY uq_strategy (strategy_id),
            KEY idx_project_keyword (project_id, keyword),
            KEY idx_project_page_type (project_id, page_type)
        ) {$charset_collate};";
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta($sql);
    }

    private function aysa_missing_page_infer_type_from_title($title) {
        $t = function_exists('mb_strtolower') ? mb_strtolower((string) $title, 'UTF-8') : strtolower((string) $title);
        if (strpos($t, 'create product page') !== false) {
            return 'product';
        }
        if (strpos($t, 'create category page') !== false) {
            return 'category';
        }
        return 'landing_page';
    }

    private function aysa_missing_page_normalize_recommended_page_type($value) {
        $text = function_exists('mb_strtolower') ? mb_strtolower(trim((string) $value), 'UTF-8') : strtolower(trim((string) $value));
        if (strpos($text, 'product') !== false) {
            return 'Product Page';
        }
        if (strpos($text, 'category') !== false) {
            return 'Category Page';
        }
        return 'Landing Page';
    }

    private function aysa_missing_page_content_type_from_recommended($recommended_page_type) {
        $normalized = $this->aysa_missing_page_normalize_recommended_page_type($recommended_page_type);
        if ($normalized === 'Product Page') {
            return 'product_page';
        }
        if ($normalized === 'Category Page') {
            return 'category_page';
        }
        return 'landing_page';
    }

    private function aysa_missing_page_extract_keyword($title, $keywords_json = '') {
        $kw = '';
        $decoded = json_decode((string) $keywords_json, true);
        if (is_array($decoded) && isset($decoded[0]) && is_string($decoded[0])) {
            $kw = trim((string) $decoded[0]);
        }
        if ($kw === '' && preg_match('/for\s+(.+)$/iu', (string) $title, $m)) {
            $kw = trim((string) ($m[1] ?? ''));
        }
        return sanitize_text_field($kw);
    }

    private function aysa_generate_missing_page_meta($project_id, $keyword, $page_type, $intent, $context = array()) {
        $prompt = $this->aysa_build_missing_page_meta_prompt($keyword, $page_type, $intent, $context);

        $broker_strategy_payload = array(
            'project_id' => $project_id,
            'strategy_type' => 'missing_page_meta',
            'payload' => array(
                'keyword' => $keyword,
                'page_type' => $page_type,
                'intent' => $intent,
                'context' => $context,
                'prompt' => $prompt,
            ),
        );
        $broker_meta_payload = array(
            'project_id' => $project_id,
            'keyword' => $keyword,
            'page_type' => $page_type,
            'intent' => $intent,
            'context' => $context,
            'prompt' => $prompt,
        );

        $title = '';
        $h1 = '';
        $meta_description = '';
        $model = '';
        $warning = '';
        $broker_error = '';
        $result = $this->aysa_broker_request('POST', '/v1/content/missing-page/meta', $broker_meta_payload, 90);
        if (is_wp_error($result)) {
            $broker_error = $result->get_error_message();
            $result = null;
        }

        if (is_array($result) && !empty($result['ok'])) {
            $broker_error = '';
            $item_payload = array();
            if (!empty($result['item']) && is_array($result['item'])) {
                $item_payload = $result['item'];
            } elseif (!empty($result['items']) && is_array($result['items']) && isset($result['items'][0]) && is_array($result['items'][0])) {
                $item_payload = $result['items'][0];
            }
            if (!empty($item_payload)) {
                $title = sanitize_text_field((string) ($item_payload['title'] ?? ''));
                $h1 = sanitize_text_field((string) ($item_payload['h1'] ?? $item_payload['heading'] ?? ''));
                $meta_description = sanitize_textarea_field((string) ($item_payload['meta_description'] ?? $item_payload['description'] ?? ''));
            }
            $model = sanitize_text_field((string) ($result['model'] ?? $model));
        } else {
            if (is_array($result)) {
                $broker_error = sanitize_text_field((string) ($result['error'] ?? $result['message'] ?? 'empty_or_invalid_broker_response'));
            } elseif ($broker_error === '') {
                $broker_error = 'empty_or_invalid_broker_response';
            }
        }

        if ($title === '' || $h1 === '' || $meta_description === '') {
            $title = $title !== '' ? $title : ucwords($keyword) . ' - Pagina recomandata';
            $h1 = $h1 !== '' ? $h1 : ucwords($keyword);
            $cta = ($page_type === 'product') ? 'Comandă acum' : 'Descoperă oferta';
            $meta_description = $meta_description !== '' ? $meta_description : ($cta . ' pentru ' . $keyword . ' și obține livrare rapidă, preț bun și suport dedicat pentru alegerea potrivită.');
            $warning = 'fallback_template_used';
            if ($broker_error !== '') {
                $warning .= ':broker=' . sanitize_text_field($broker_error);
            }
        }

        return array(
            'keyword' => $keyword,
            'page_type' => $page_type,
            'intent' => $intent,
            'title' => $title,
            'h1' => $h1,
            'meta_description' => $meta_description,
            'model' => $model,
            'warning' => $warning,
            'source' => ($warning === '' ? 'ai' : 'fallback'),
            'openai_error' => '',
            'broker_error' => $broker_error,
            'prompt_preview' => $prompt,
        );
    }

    private function aysa_save_missing_page_meta($project_id, $strategy_id, $meta) {
        global $wpdb;
        $this->aysa_missing_page_meta_maybe_create_table();
        $table = esc_sql($this->aysa_missing_page_meta_table());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $wpdb->replace(
            $table,
            array(
                'project_id' => intval($project_id),
                'strategy_id' => intval($strategy_id) > 0 ? intval($strategy_id) : 0,
                'keyword' => sanitize_text_field((string) ($meta['keyword'] ?? '')),
                'page_type' => sanitize_key((string) ($meta['page_type'] ?? 'landing_page')),
                'intent' => sanitize_text_field((string) ($meta['intent'] ?? 'N/A')),
                'seo_title' => sanitize_text_field((string) ($meta['title'] ?? '')),
                'h1' => sanitize_text_field((string) ($meta['h1'] ?? '')),
                'meta_description' => sanitize_textarea_field((string) ($meta['meta_description'] ?? '')),
                'model' => sanitize_text_field((string) ($meta['model'] ?? '')),
                'warning' => sanitize_text_field((string) ($meta['warning'] ?? '')),
                'updated_at' => current_time('mysql'),
            ),
            array('%d', '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')
        );
    }

    public function handle_missing_page_generate_meta() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_content_strategy_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        $keyword = sanitize_text_field((string) wp_unslash($_POST['keyword'] ?? ''));
        if ($keyword === '') {
            wp_send_json_error('Missing keyword');
            return;
        }

        $page_type = sanitize_key((string) wp_unslash($_POST['page_type'] ?? 'landing_page'));
        $allowed_types = array('category', 'product', 'landing_page');
        if (!in_array($page_type, $allowed_types, true)) {
            $page_type = 'landing_page';
        }

        $intent = strtoupper(sanitize_text_field((string) wp_unslash($_POST['intent'] ?? 'N/A')));
        if (!in_array($intent, array('I', 'C', 'T', 'N', 'N/A'), true)) {
            $intent = 'N/A';
        }
        $strategy_id = intval(wp_unslash($_POST['strategy_id'] ?? 0));

        $user_id = $this->aysa_get_effective_setup_user_id();
        $context = $this->aysa_keyword_strategy_get_context($user_id);
        $general = get_option('aysa_wizard_general_' . $user_id, array());
        if (is_array($general)) {
            $context['brand_name'] = (string) ($general['brand_name'] ?? $general['brand'] ?? $general['company_name'] ?? '');
            $context['target_audience'] = (string) ($general['company_info'] ?? $general['business_information'] ?? '');
        }
        $meta = $this->aysa_generate_missing_page_meta($project_id, $keyword, $page_type, $intent, $context);

        if ($strategy_id > 0) {
            global $wpdb;
            $table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->update(
                $table,
                array(
                    'title' => sanitize_text_field((string) ($meta['title'] ?? '')),
                    'last_error' => sanitize_text_field((string) ($meta['h1'] ?? '')) . ' || ' . sanitize_textarea_field((string) ($meta['meta_description'] ?? '')),
                    'execution_stage' => 'seo_meta_ready',
                    'automation_updated_at' => current_time('mysql'),
                ),
                array(
                    'id' => $strategy_id,
                    'project_id' => $project_id,
                ),
                array('%s', '%s', '%s', '%s'),
                array('%d', '%d')
            );
            $this->aysa_save_missing_page_meta($project_id, $strategy_id, $meta);
        }

        // Track credit usage only when AI generation actually happened.
        if (($meta['source'] ?? '') === 'ai') {
            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_missing_page_generate_meta',
                'missing_page_generate_meta_' . md5($keyword . '|' . $page_type . '|' . $intent),
                array(
                    'module' => 'missing_pages',
                    'keyword' => $keyword,
                    'page_type' => $page_type,
                    'intent' => $intent,
                    'strategy_id' => $strategy_id,
                    'source' => 'ai',
                ),
                55,
                'wp_ajax/aysa_missing_page_generate_meta'
            );
        }

        wp_send_json_success($meta);
    }

    public function handle_missing_pages_refactor_titles() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_content_strategy_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Access denied');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $raw_items = wp_unslash($_POST['items'] ?? array());
        if (is_string($raw_items)) {
            $decoded = json_decode(wp_unslash($raw_items), true);
            $raw_items = is_array($decoded) ? $decoded : array();
        }
        if (!is_array($raw_items) || empty($raw_items)) {
            wp_send_json_error('No rows to refactor');
            return;
        }

        global $wpdb;
        $strategy_table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        $rows_for_api = array();
        foreach ($raw_items as $item) {
            if (!is_array($item)) {
                continue;
            }
            $strategy_id = intval($item['id'] ?? 0);
            if ($strategy_id <= 0) {
                continue;
            }
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $row = $wpdb->get_row($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT id, title, keywords, intent
" .
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
                "                 FROM {$strategy_table}
                 WHERE id = %d
                   AND project_id = %d
                   AND strategy_type = 'missing_pages'
                 LIMIT 1",
                $strategy_id,
                $project_id
            ), ARRAY_A);
            if (!is_array($row)) {
                continue;
            }
            $rows_for_api[] = array(
                'id' => $strategy_id,
                'title' => (string) ($row['title'] ?? ''),
                'keywords' => (string) ($row['keywords'] ?? ''),
                'intent' => (string) ($item['intent'] ?? ($row['intent'] ?? 'N/A')),
                'keyword' => (string) ($item['keyword'] ?? ''),
                'page_type' => (string) ($item['page_type'] ?? ''),
            );
        }

        if (empty($rows_for_api)) {
            wp_send_json_success(array(
                'updated' => 0,
                'failed' => 0,
                'ai' => 0,
                'fallback' => 0,
                'total' => 0,
            ));
            return;
        }

        $setup_user_id = $this->aysa_get_effective_setup_user_id();
        $result = $this->aysa_broker_request('POST', '/v1/content-strategy/missing-pages-refactor-titles', array(
            'project_id' => $project_id,
            'rows' => $rows_for_api,
            'raw_context' => $this->aysa_collect_content_strategy_context_bundle($project_id, $setup_user_id),
        ), 90);
        if (is_wp_error($result) || !is_array($result) || empty($result['ok'])) {
            wp_send_json_error(is_wp_error($result) ? $result->get_error_message() : 'Refactor failed');
            return;
        }

        $updated = 0;
        $failed = intval($result['failed'] ?? 0);
        $ai_count = intval($result['ai'] ?? 0);
        $fallback_count = intval($result['fallback'] ?? 0);
        $updates = isset($result['updates']) && is_array($result['updates']) ? $result['updates'] : array();

        foreach ($updates as $update_row) {
            if (!is_array($update_row)) {
                continue;
            }
            $strategy_id = intval($update_row['id'] ?? 0);
            if ($strategy_id <= 0) {
                $failed++;
                continue;
            }
            $meta = isset($update_row['meta']) && is_array($update_row['meta']) ? $update_row['meta'] : array();
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $ok = $wpdb->update(
                $strategy_table,
                array(
                    'title' => sanitize_text_field((string) ($update_row['title'] ?? ($meta['title'] ?? ''))),
                    'last_error' => sanitize_text_field((string) ($update_row['last_error'] ?? '')),
                    'automation_updated_at' => current_time('mysql'),
                ),
                array(
                    'id' => $strategy_id,
                    'project_id' => $project_id,
                ),
                array('%s', '%s', '%s'),
                array('%d', '%d')
            );
            if ($ok === false) {
                $failed++;
                continue;
            }
            if (!empty($meta)) {
                $this->aysa_save_missing_page_meta($project_id, $strategy_id, $meta);
            }
            $updated++;
        }

        // Track credit usage only if this batch used AI for at least one row.
        if ($ai_count > 0) {
            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_missing_pages_refactor_titles',
                'missing_pages_refactor_titles_batch_' . md5(wp_json_encode(array(
                    'project_id' => $project_id,
                    'total' => count($raw_items),
                    'updated' => $updated,
                    'ai' => $ai_count,
                ))),
                array(
                    'module' => 'missing_pages',
                    'updated' => $updated,
                    'failed' => $failed,
                    'ai' => $ai_count,
                    'fallback' => $fallback_count,
                    'requested_rows' => count($raw_items),
                ),
                55,
                '/v1/content-strategy/missing-pages-refactor-titles'
            );
        }

        wp_send_json_success(array(
            'updated' => $updated,
            'failed' => $failed,
            'ai' => $ai_count,
            'fallback' => $fallback_count,
            'total' => $updated + $failed,
        ));
    }

    private function aysa_keyword_strategy_calculate_seed_relevance($keyword, $seed_keywords) {
        $kw = $this->aysa_keyword_strategy_normalize_keyword($keyword);
        if ($kw === '' || !is_array($seed_keywords) || empty($seed_keywords)) {
            return 0;
        }

        $kw_tokens = array_values(array_filter(explode(' ', $kw)));
        if (empty($kw_tokens)) {
            return 0;
        }

        $best = 0;
        foreach ($seed_keywords as $seed) {
            $seed_norm = $this->aysa_keyword_strategy_normalize_keyword($seed);
            if ($seed_norm === '') {
                continue;
            }
            if ($kw === $seed_norm) {
                return 100;
            }
            if (strpos($kw, $seed_norm) !== false || strpos($seed_norm, $kw) !== false) {
                $best = max($best, 85);
                continue;
            }

            $seed_tokens = array_values(array_filter(explode(' ', $seed_norm)));
            if (empty($seed_tokens)) {
                continue;
            }
            $common = array_intersect($kw_tokens, $seed_tokens);
            if (!empty($common)) {
                $coverage = count($common) / max(1, count($seed_tokens));
                $score = intval(round($coverage * 70));
                $best = max($best, $score);
            }
        }
        return min(100, max(0, $best));
    }

    private function aysa_keyword_strategy_build_local_plan($project_id, $user_id, $seed_keywords, $competitors, $context = array()) {
        $rows = $this->aysa_keyword_strategy_collect_local_keyword_rows($project_id);
        $association_map = $this->aysa_keyword_strategy_get_association_map($project_id);

        $seed_norm_map = array();
        foreach ($seed_keywords as $seed) {
            $norm = $this->aysa_keyword_strategy_normalize_keyword($seed);
            if ($norm !== '') {
                $seed_norm_map[$norm] = (string) $seed;
            }
        }

        $by_norm = array();
        foreach ($rows as $row) {
            $keyword = trim((string) ($row['keyword'] ?? ''));
            $norm = $this->aysa_keyword_strategy_normalize_keyword($keyword);
            if ($norm === '') {
                continue;
            }
            if (!isset($by_norm[$norm])) {
                $by_norm[$norm] = $row;
            }
        }

        foreach ($seed_keywords as $seed) {
            $norm = $this->aysa_keyword_strategy_normalize_keyword($seed);
            if ($norm === '' || isset($by_norm[$norm])) {
                continue;
            }
            $by_norm[$norm] = array(
                'keyword' => $seed,
                'search_volume' => null,
                'difficulty' => null,
                'intent' => '',
                'competition' => '',
                'traffic' => null,
                'traffic_pct' => null,
                'position' => null,
                'cpc' => null,
                'target_url' => '',
                'updated_at' => current_time('mysql'),
            );
        }

        $candidates = array();
        foreach ($by_norm as $norm => $row) {
            $relevance = $this->aysa_keyword_strategy_calculate_seed_relevance((string) ($row['keyword'] ?? ''), $seed_keywords);
            if (!isset($seed_norm_map[$norm]) && $relevance < 35) {
                continue;
            }
            $candidates[$norm] = array(
                'keyword' => (string) ($row['keyword'] ?? ''),
                'search_volume' => is_null($row['search_volume']) ? null : intval($row['search_volume']),
                'kd' => is_null($row['difficulty']) ? null : floatval($row['difficulty']),
                'intent' => (string) ($row['intent'] ?? ''),
                'traffic' => is_null($row['traffic']) ? null : intval($row['traffic']),
                'traffic_pct' => is_null($row['traffic_pct']) ? null : floatval($row['traffic_pct']),
                'position' => is_null($row['position']) ? null : floatval($row['position']),
                'cpc' => is_null($row['cpc']) ? null : floatval($row['cpc']),
                'target_url' => (string) ($row['target_url'] ?? ''),
                'updated_at' => (string) ($row['updated_at'] ?? ''),
                'seed_relevance' => $relevance,
                'source' => isset($seed_norm_map[$norm]) ? 'seed' : 'related_local',
            );
        }

        $items = array();
        foreach ($candidates as $norm => $row) {
            $assoc_list = isset($association_map[$norm]) && is_array($association_map[$norm]) ? $association_map[$norm] : array();
            $has_association = !empty($assoc_list);
            $has_target_url = trim((string) ($row['target_url'] ?? '')) !== '';
            $volume = is_null($row['search_volume']) ? 0 : intval($row['search_volume']);
            $kd = is_null($row['kd']) ? 60.0 : floatval($row['kd']);
            $traffic = is_null($row['traffic']) ? 0 : intval($row['traffic']);

            $recommended_action = 'enhance_existing';
            $reason = 'No direct page association found yet.';

            if ($has_association) {
                $recommended_action = 'associate_existing';
                $reason = 'Keyword already has an associated page/product/category.';
            } elseif ($has_target_url) {
                $recommended_action = 'associate_existing';
                $reason = 'Matched URL exists in keyword metrics and should be associated.';
            } elseif ($volume >= 40 && $kd <= 60.0) {
                $recommended_action = 'create_new_page';
                $reason = 'No current match and opportunity score suggests a dedicated page.';
            } else {
                $recommended_action = 'enhance_existing';
                $reason = 'Best next step is to strengthen existing relevant pages before creating new ones.';
            }

            $priority = 0;
            $priority += min(50, $volume);
            $priority += min(20, max(0, 20 - intval(round($kd / 5))));
            $priority += min(20, $traffic * 2);
            $priority += min(10, intval(round(($row['seed_relevance'] ?? 0) / 10)));
            if ($recommended_action === 'create_new_page') {
                $priority += 8;
            } elseif ($recommended_action === 'associate_existing') {
                $priority += 5;
            }

            $items[] = array(
                'keyword' => $row['keyword'],
                'source' => $row['source'],
                'seed_relevance' => intval($row['seed_relevance']),
                'intent' => ($row['intent'] !== '' ? $row['intent'] : 'N/A'),
                'search_volume' => ($volume > 0 ? $volume : null),
                'kd' => (!is_null($row['kd']) ? round(floatval($row['kd']), 2) : null),
                'traffic' => ($traffic > 0 ? $traffic : null),
                'traffic_pct' => (!is_null($row['traffic_pct']) ? round(floatval($row['traffic_pct']), 4) : null),
                'position' => (!is_null($row['position']) ? round(floatval($row['position']), 2) : null),
                'cpc' => (!is_null($row['cpc']) ? round(floatval($row['cpc']), 2) : null),
                'target_url' => ($has_target_url ? $row['target_url'] : ''),
                'associations' => array_values($assoc_list),
                'recommended_action' => $recommended_action,
                'reason' => $reason,
                'priority_score' => intval($priority),
                'status' => 'pending',
                'updated_at' => $row['updated_at'],
            );
        }

        usort($items, function($a, $b) {
            return intval($b['priority_score'] ?? 0) <=> intval($a['priority_score'] ?? 0);
        });

        $summary = array(
            'total_keywords' => count($items),
            'associate_existing' => 0,
            'enhance_existing' => 0,
            'create_new_page' => 0,
        );
        foreach ($items as $item) {
            $action = (string) ($item['recommended_action'] ?? '');
            if (isset($summary[$action])) {
                $summary[$action] += 1;
            }
        }

        return array(
            'project_id' => intval($project_id),
            'user_id' => intval($user_id),
            'generated_at' => current_time('mysql'),
            'source' => 'local_fallback',
            'seed_keywords' => array_values($seed_keywords),
            'competitors' => array_values($competitors),
            'context' => is_array($context) ? $context : array(),
            'summary' => $summary,
            'items' => array_values($items),
        );
    }

    public function handle_keyword_discovery_get_strategy() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }
        if (!$this->check_authentication(false)) {
            wp_send_json_error('Authentication required');
            return;
        }

        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }
        $auth_manager = Aysa_Auth_Manager::get_instance();
        $auth_status = $auth_manager->is_authenticated();
        $user_id = intval($auth_status['aysa_user_id'] ?? 0);
        if ($user_id <= 0) {
            wp_send_json_error('Invalid authentication context');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        $option_key = $this->aysa_keyword_strategy_option_key($project_id, $user_id);
        $saved = get_option($option_key, array());
        if (!is_array($saved)) {
            $saved = array();
        }

        wp_send_json_success(array(
            'project_id' => $project_id,
            'user_id' => $user_id,
            'strategy' => $saved,
            'has_strategy' => !empty($saved),
        ));
    }

    public function handle_keyword_discovery_generate_strategy() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }
        if (!$this->check_authentication(false)) {
            wp_send_json_error('Authentication required');
            return;
        }

        if (!class_exists('Aysa_Auth_Manager')) {
            require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
        }
        $auth_manager = Aysa_Auth_Manager::get_instance();
        $auth_status = $auth_manager->is_authenticated();
        $user_id = intval($auth_status['aysa_user_id'] ?? 0);
        if ($user_id <= 0) {
            wp_send_json_error('Invalid authentication context');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        $seed_data = $this->aysa_keyword_strategy_extract_seed_data($user_id);
        $seed_keywords = $seed_data['seed_keywords'];
        $competitors = $seed_data['competitors'];
        if (empty($seed_keywords)) {
            wp_send_json_error('No seed keywords found in Setup SEO Profile > Competition.');
            return;
        }

        $request = $this->aysa_research_async_prepare_keyword_strategy_request(array(
            'project_id' => $project_id,
        ));
        if (is_wp_error($request)) {
            wp_send_json_error($request->get_error_message());
            return;
        }

        $job = $this->aysa_project_jobs_enqueue(
            $project_id,
            'research',
            'keyword_strategy_generate',
            array(
                'task' => 'keyword_strategy_generate',
                'request' => $request,
            ),
            array(
                'dedupe_key' => (string) ($request['request_hash'] ?? ''),
                'input_summary' => array(
                    'seed_keywords_count' => count($seed_keywords),
                    'competitors_count' => count($competitors),
                ),
            )
        );
        if (is_wp_error($job)) {
            wp_send_json_error($job->get_error_message());
            return;
        }
        $job_row = isset($job['job']) && is_array($job['job']) ? $job['job'] : array();
        wp_send_json_success(array(
            'project_id' => $project_id,
            'job_id' => (string) ($job_row['job_id'] ?? ''),
            'status' => (string) ($job_row['status'] ?? 'queued'),
            'worker_ref' => (string) ($job_row['worker_ref'] ?? ''),
            'project_job_rack' => isset($job['rack']) && is_array($job['rack']) ? $job['rack'] : array(),
        ));
    }

    public function handle_keyword_discovery_refresh_start_background() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        $apply_to_wp = !isset($_POST['apply_to_wp']) || in_array(strtolower((string) wp_unslash($_POST['apply_to_wp'])), array('1', 'true', 'yes'), true);
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $refresh_kind = strtolower(trim((string) wp_unslash($_POST['refresh_kind'] ?? 'gsc_auto')));
        $job = $this->aysa_project_jobs_enqueue(
            $project_id,
            'research',
            'keyword_discovery_refresh',
            array(
                'apply_to_wp' => $apply_to_wp,
                'refresh_kind' => $refresh_kind,
            ),
            array(
                'dedupe_key' => md5(wp_json_encode(array(
                    'job_type' => 'keyword_discovery_refresh',
                    'refresh_kind' => $refresh_kind,
                    'apply_to_wp' => $apply_to_wp ? 1 : 0,
                ))),
                'input_summary' => array(
                    'refresh_kind' => $refresh_kind,
                    'apply_to_wp' => $apply_to_wp ? 1 : 0,
                ),
            )
        );
        if (is_wp_error($job)) {
            wp_send_json_error($job->get_error_message());
            return;
        }
        $job_row = isset($job['job']) && is_array($job['job']) ? $job['job'] : array();
        $status = array();
        if ((string) ($job_row['status'] ?? '') === 'running' && !empty($job_row['worker_ref'])) {
            $parts = explode(':', (string) $job_row['worker_ref']);
            $run_id = (string) end($parts);
            if ($run_id !== '') {
                $status = $this->aysa_keyword_discovery_get_status($project_id);
            }
        }

        wp_send_json_success(array(
            'started' => (string) ($job_row['status'] ?? '') === 'running',
            'already_running' => !empty($job['deduped']),
            'status' => $status,
            'pending_item' => array(
                'item_key' => 'project_job:' . (string) ($job_row['job_id'] ?? ''),
                'item_type' => 'project_job',
                'post_id' => $project_id,
                'title' => 'Keyword refresh',
            ),
            'project_job_rack' => isset($job['rack']) && is_array($job['rack']) ? $job['rack'] : array(),
        ));
    }

    public function handle_keyword_discovery_refresh_status() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }
        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        $status = $this->aysa_keyword_discovery_get_status($project_id);
        if (($status['status'] ?? 'idle') === 'idle') {
            $job = $this->aysa_project_jobs_find_latest_job($project_id, 'research', 'keyword_discovery_refresh', array('queued', 'running', 'completed', 'failed'));
            if (is_array($job)) {
                $status = array(
                    'run_id' => (string) ($job['job_id'] ?? ''),
                    'refresh_kind' => 'gsc_auto',
                    'status' => (string) ($job['status'] ?? 'idle'),
                    'total' => 0,
                    'processed' => 0,
                    'staged' => 0,
                    'inserted' => 0,
                    'updated' => 0,
                    'apply_to_wp' => 1,
                    'last_error' => (string) ($job['error_summary'] ?? ''),
                    'metrics_notice' => '',
                    'notification_sent' => '0',
                    'started_at' => intval($job['started_at'] ?? 0),
                    'updated_at' => intval($job['updated_at'] ?? 0),
                    'finished_at' => intval($job['finished_at'] ?? 0),
                    'worker_ref' => (string) ($job['worker_ref'] ?? ''),
                );
            }
        }
        $status['project_job_rack'] = $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state($project_id));
        wp_send_json_success($status);
    }

    public function handle_keyword_discovery_apply_staged_to_local() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }
        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        $snapshot = get_option($this->aysa_keyword_discovery_snapshot_key($project_id), array());
        $keywords = (is_array($snapshot) && isset($snapshot['keywords']) && is_array($snapshot['keywords'])) ? $snapshot['keywords'] : array();
        if (empty($keywords)) {
            wp_send_json_success(array(
                'applied' => false,
                'inserted' => 0,
                'updated' => 0,
                'count' => 0,
                'message' => 'No staged keywords available.',
            ));
            return;
        }

        $persist = $this->aysa_keyword_discovery_persist_keywords($project_id, $keywords);
        wp_send_json_success(array(
            'applied' => true,
            'inserted' => intval($persist['inserted'] ?? 0),
            'updated' => intval($persist['updated'] ?? 0),
            'count' => count($keywords),
            'snapshot_fetched_at' => intval($snapshot['fetched_at'] ?? 0),
        ));
    }

    private function aysa_keyword_discovery_get_seed_keywords_with_fallback($user_id) {
        $seed_data = $this->aysa_keyword_strategy_extract_seed_data($user_id);
        $seed_keywords = (is_array($seed_data) && isset($seed_data['seed_keywords']) && is_array($seed_data['seed_keywords']))
            ? $seed_data['seed_keywords']
            : array();

        $seed_keywords = array_values(array_unique(array_filter(array_map(function($kw) {
            return trim((string) $kw);
        }, $seed_keywords), function($kw) {
            return $kw !== '';
        })));

        // Strict isolation: do not fall back to other users' Setup data.
        // If current user's Setup SEO Profile has no seed keywords, return empty.
        return $seed_keywords;
    }

    private function aysa_keyword_discovery_fetch_keyword_suggestions($project_id, $seed_keywords, $context) {
        $payload = array(
            'project_id' => intval($project_id),
            'seed_keywords' => array_values($seed_keywords),
            'location_code' => intval($context['location_code'] ?? 2840),
            'language_code' => (string) ($context['language_code'] ?? 'ro'),
            'limit_per_seed' => 80,
            'max_keywords' => 2000,
        );

        $paths = array(
            '/v1/keywords/suggestions',
        );

        foreach ($paths as $path) {
            $result = $this->aysa_broker_request('POST', $path, $payload, 120);
            if (is_wp_error($result)) {
                continue;
            }
            $rows = $this->aysa_keyword_discovery_extract_metrics_rows($result);
            if (!empty($rows)) {
                return $rows;
            }
        }

        return array();
    }

    public function handle_keyword_discovery_keyword_suggestions_refresh() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        // Setup SEO Profile is stored by authenticated external user id (aysa_user_id),
        // not by WordPress user id. Use the same identity here to avoid false "no seed keywords".
        $setup_user_id = intval($this->aysa_get_authenticated_external_user_id());
        if ($setup_user_id <= 0) {
            $setup_user_id = get_current_user_id();
        }
        $seed_keywords = $this->aysa_keyword_discovery_get_seed_keywords_with_fallback($setup_user_id);
        if (empty($seed_keywords)) {
            wp_send_json_error('No seed keywords found in Setup SEO Profile > Competition.');
            return;
        }

        $result = $this->aysa_keyword_discovery_refresh_keyword_suggestions($project_id, $setup_user_id, true);
        if (is_wp_error($result)) {
            wp_send_json_error($result->get_error_message());
            return;
        }

        wp_send_json_success(array(
            'project_id' => $project_id,
            'seed_keywords' => $seed_keywords,
            'count' => intval($result['count'] ?? 0),
            'inserted' => intval($result['inserted'] ?? 0),
            'updated' => intval($result['updated'] ?? 0),
        ));
    }

    public function handle_keyword_discovery_v2_gsc_raw() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_keyword_cache_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        $force_refresh = !empty($_POST['force_refresh']);
        if (!$force_refresh) {
            $cached = $this->aysa_keyword_v2_cache_get($project_id, 'gsc_v2');
            if (is_array($cached)) {
                wp_send_json_success($cached);
                return;
            }
        }

        $site_host = wp_parse_url(home_url(), PHP_URL_HOST);
        $site_host = is_string($site_host) ? $site_host : '';

        $raw = $this->aysa_broker_request('POST', '/v1/google/keyword-mappings', array(
            'project_id' => $project_id,
            'site_host' => $site_host,
            'days' => 90,
            'limit' => 1000,
        ), 90);

        if (is_wp_error($raw)) {
            wp_send_json_error($raw->get_error_message());
            return;
        }
        if (!is_array($raw) || empty($raw['connected'])) {
            $reason = is_array($raw) && !empty($raw['error']) ? (string) $raw['error'] : 'Could not fetch raw GSC rows.';
            wp_send_json_error($reason);
            return;
        }

        $query_rows = isset($raw['gsc_queries']) && is_array($raw['gsc_queries']) ? $raw['gsc_queries'] : array();
        $mapping_rows = isset($raw['gsc_mappings']) && is_array($raw['gsc_mappings']) ? $raw['gsc_mappings'] : array();

        $rows = array();
        $mapped_queries = array();

        foreach ($mapping_rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keyword = trim((string) ($row['query'] ?? $row['keyword'] ?? ''));
            if ($keyword === '') {
                continue;
            }
            $mapped_queries[strtolower($keyword)] = true;
            $rows[] = array(
                'keyword' => $keyword,
                'page' => trim((string) ($row['page'] ?? $row['url'] ?? '')),
                'landing_page' => trim((string) ($row['page'] ?? $row['url'] ?? '')),
                'clicks' => intval($row['clicks'] ?? 0),
                'impressions' => intval($row['impressions'] ?? 0),
                'ctr' => isset($row['ctr']) ? floatval($row['ctr']) : null,
                'position' => isset($row['position']) ? floatval($row['position']) : null,
                'row_type' => 'query_page',
                'source' => 'gsc',
            );
        }

        foreach ($query_rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keyword = trim((string) ($row['query'] ?? $row['keyword'] ?? ''));
            if ($keyword === '') {
                continue;
            }
            if (isset($mapped_queries[strtolower($keyword)])) {
                continue;
            }
            $rows[] = array(
                'keyword' => $keyword,
                'page' => '',
                'landing_page' => '',
                'clicks' => intval($row['clicks'] ?? 0),
                'impressions' => intval($row['impressions'] ?? 0),
                'ctr' => isset($row['ctr']) ? floatval($row['ctr']) : null,
                'position' => isset($row['position']) ? floatval($row['position']) : null,
                'row_type' => 'query_only',
                'source' => 'gsc',
            );
        }

        usort($rows, static function($a, $b) {
            $clickDiff = intval($b['clicks'] ?? 0) <=> intval($a['clicks'] ?? 0);
            if ($clickDiff !== 0) {
                return $clickDiff;
            }
            $impressionDiff = intval($b['impressions'] ?? 0) <=> intval($a['impressions'] ?? 0);
            if ($impressionDiff !== 0) {
                return $impressionDiff;
            }
            return strcmp((string) ($a['keyword'] ?? ''), (string) ($b['keyword'] ?? ''));
        });

        $response = array(
            'rows' => $rows,
            'count' => count($rows),
            'diagnostics' => array(
                'connected' => !empty($raw['connected']),
                'selected_gsc_site' => (string) ($raw['selected_gsc_site'] ?? ''),
                'used_gsc_site' => (string) ($raw['used_gsc_site'] ?? ''),
                'gsc_queries_count' => count($query_rows),
                'gsc_mappings_count' => count($mapping_rows),
            ),
            'cache_meta' => array(
                'source' => 'live_refresh',
                'cached_at' => gmdate('c'),
            ),
            'quality_flags' => array_values(array_filter(array(
                empty($mapping_rows) ? 'no_query_page_rows' : '',
                empty($query_rows) ? 'no_query_only_rows' : '',
            ))),
            'explainability' => array(
                'included_pool' => 'Rows include mapped query-page pairs plus standalone GSC queries that are not already mapped.',
                'excluded_pool' => 'Duplicate GSC queries already represented by query-page rows are excluded from the standalone query pool.',
            ),
            'workspace_handoff' => array(
                'consumer' => 'keyword_mapping_v2',
                'source_tag' => 'gsc',
            ),
        );
        $response = $this->aysa_keyword_v2_finalize_snapshot_contract($project_id, 'gsc_v2', $response, 'rows');
        $this->aysa_keyword_v2_cache_set($project_id, 'gsc_v2', $response);
        wp_send_json_success($response);
    }

    public function handle_keyword_discovery_v2_dataforseo_raw() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_keyword_cache_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        $force_refresh = !empty($_POST['force_refresh']);
        if (!$force_refresh) {
            $cached = $this->aysa_keyword_v2_cache_get($project_id, 'dataforseo_v2');
            if (is_array($cached)) {
                wp_send_json_success($cached);
                return;
            }
        }

        $setup_user_id = intval($this->aysa_get_authenticated_external_user_id());
        if ($setup_user_id <= 0) {
            $setup_user_id = get_current_user_id();
        }
        $seed_keywords = $this->aysa_keyword_discovery_get_seed_keywords_with_fallback($setup_user_id);
        if (empty($seed_keywords)) {
            wp_send_json_error('No seed keywords found in Setup SEO Profile > Competition.');
            return;
        }

        $context = $this->aysa_monitoring_resolve_context('');
        $payload = array(
            'project_id' => intval($project_id),
            'seed_keywords' => array_values($seed_keywords),
            'location_code' => intval($context['location_code'] ?? 2840),
            'language_code' => (string) ($context['language_code'] ?? 'ro'),
            'limit_per_seed' => 1000,
            'max_keywords' => 10000,
            'raw_mode' => true,
        );

        $paths = array(
            '/v1/keywords/suggestions',
        );

        $rows = array();
        $provider = '';
        foreach ($paths as $path) {
            $result = $this->aysa_broker_request('POST', $path, $payload, 180);
            if (is_wp_error($result) || !is_array($result)) {
                continue;
            }
            $candidate_rows = isset($result['rows']) && is_array($result['rows']) ? $result['rows'] : array();
            if (!empty($candidate_rows)) {
                $rows = $candidate_rows;
                $provider = (string) ($result['provider'] ?? '');
                break;
            }
        }

        if (empty($rows)) {
            wp_send_json_error('Nu am primit rânduri de keywords pentru această analiză.');
            return;
        }

        $normalized = array();
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keyword = trim((string) ($row['keyword'] ?? ''));
            if ($keyword === '') {
                continue;
            }
            $normalized[] = array(
                'keyword' => $keyword,
                'seed_keyword' => trim((string) ($row['seed_keyword'] ?? '')),
                'source' => trim((string) ($row['source'] ?? '')),
                'intent' => trim((string) ($row['intent'] ?? '')),
                'search_volume' => isset($row['search_volume']) ? intval($row['search_volume']) : null,
                'kd' => isset($row['kd']) ? floatval($row['kd']) : null,
                'cpc' => isset($row['cpc']) ? floatval($row['cpc']) : null,
                'item_types' => isset($row['item_types']) && is_array($row['item_types']) ? array_values($row['item_types']) : array(),
            );
        }

        $response = array(
            'rows' => $normalized,
            'count' => count($normalized),
            'provider' => $provider,
            'seed_keywords' => array_values($seed_keywords),
            'cache_meta' => array(
                'source' => 'live_refresh',
                'cached_at' => gmdate('c'),
            ),
            'quality_flags' => array_values(array_filter(array(
                empty($normalized) ? 'empty_keyword_rows' : '',
                $provider === '' ? 'provider_unknown' : '',
            ))),
            'explainability' => array(
                'included_pool' => 'Rows combine keyword suggestions and related keywords normalized into one discovery pool.',
                'excluded_pool' => 'Rows without a valid keyword string are dropped before caching.',
            ),
            'workspace_handoff' => array(
                'consumer' => 'keyword_mapping_v2',
                'source_tag' => 'dataforseo',
                'seed_keywords' => array_values($seed_keywords),
            ),
        );
        $response = $this->aysa_keyword_v2_finalize_snapshot_contract($project_id, 'dataforseo_v2', $response, 'rows');
        $this->aysa_keyword_v2_cache_set($project_id, 'dataforseo_v2', $response);
        wp_send_json_success($response);
    }

    public function handle_keyword_discovery_v2_competitor_gap_raw() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_keyword_cache_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        $force_refresh = !empty($_POST['force_refresh']);
        if (!$force_refresh) {
            $cached = $this->aysa_keyword_v2_cache_get($project_id, 'competitor_gap_v2');
            if (is_array($cached)) {
                wp_send_json_success($cached);
                return;
            }
        }

        $setup_user_id = intval($this->aysa_get_authenticated_external_user_id());
        if ($setup_user_id <= 0) {
            $setup_user_id = get_current_user_id();
        }

        $seed_data = $this->aysa_keyword_strategy_extract_seed_data($setup_user_id);
        $raw_competitors = isset($seed_data['competitors']) && is_array($seed_data['competitors'])
            ? array_values(array_filter(array_map(static function($value) {
                return trim((string) $value);
            }, $seed_data['competitors']), static function($value) {
                return $value !== '';
            }))
            : array();

        if (empty($raw_competitors)) {
            wp_send_json_error('No competitors found in Setup SEO Profile > Competition.');
            return;
        }

        $target = (string) wp_parse_url(home_url('/'), PHP_URL_HOST);
        $target = trim($target);
        if ($target === '') {
            wp_send_json_error('Could not resolve target host.');
            return;
        }

        $context = $this->aysa_monitoring_resolve_context($target);
        $resolved_target = trim((string) ($context['target'] ?? ''));
        if ($resolved_target === '') {
            $resolved_target = $target;
        }

        $competitors = array();
        foreach ($raw_competitors as $competitor) {
            $host = $this->aysa_monitoring_extract_host((string) $competitor);
            if ($host === '' || $this->aysa_monitoring_is_local_host($host)) {
                continue;
            }
            if ($resolved_target !== '' && strtolower($host) === strtolower($resolved_target)) {
                continue;
            }
            $competitors[] = $host;
        }
        $competitors = array_values(array_unique($competitors));

        if (empty($competitors)) {
            wp_send_json_error('No valid competitor domains found in Setup SEO Profile > Competition.');
            return;
        }

        $payload = array(
            'project_id' => $project_id,
            'target' => $resolved_target,
            'location_code' => intval($context['location_code'] ?? 2840),
            'language_code' => (string) ($context['language_code'] ?? 'ro'),
            'limit' => 2000,
            'competitors' => array_values($competitors),
        );

        $paths = array(
            '/v1/keywords/gap/v2',
        );

        $result = null;
        $broker_errors = array();
        foreach ($paths as $path) {
            $candidate = $this->aysa_broker_request('POST', $path, $payload, 180);
            if (is_wp_error($candidate)) {
                $broker_errors[] = $path . ': ' . $candidate->get_error_message();
                continue;
            }
            if (is_array($candidate) && isset($candidate['data']) && is_array($candidate['data'])) {
                $candidate = $candidate['data'];
            }
            if (is_array($candidate) && isset($candidate['rows']) && is_array($candidate['rows'])) {
                $result = $candidate;
                break;
            }
            if (is_array($candidate) && isset($candidate['error'])) {
                $broker_errors[] = $path . ': ' . (string) $candidate['error'];
            } else {
                $broker_errors[] = $path . ': unexpected_response';
            }
        }

        if (!is_array($result)) {
            $message = 'Competitor gap returned no keyword rows.';
            if (!empty($broker_errors)) {
                $message .= ' ' . implode(' | ', array_slice($broker_errors, 0, 4));
            }
            wp_send_json_error($message);
            return;
        }
        $response = array(
            'rows' => array_values(isset($result['rows']) && is_array($result['rows']) ? $result['rows'] : array()),
            'count' => intval($result['count'] ?? 0),
            'competitors' => array_values(isset($result['competitors']) && is_array($result['competitors']) ? $result['competitors'] : $competitors),
            'our_keywords_count' => intval($result['our_keywords_count'] ?? 0),
            'missing_count' => intval($result['missing_count'] ?? 0),
            'common_count' => intval($result['common_count'] ?? 0),
            'provider' => (string) ($result['provider'] ?? ''),
            'cache_meta' => array(
                'source' => 'live_refresh',
                'cached_at' => gmdate('c'),
            ),
            'quality_flags' => array_values(array_filter(array(
                empty($result['rows']) ? 'empty_gap_rows' : '',
                empty($competitors) ? 'no_competitors' : '',
            ))),
            'explainability' => array(
                'included_pool' => 'Rows represent competitor gap keywords returned by the v2 gap endpoint for configured competitors.',
                'excluded_pool' => 'Invalid competitor hosts and empty keyword rows are excluded before caching.',
            ),
            'workspace_handoff' => array(
                'consumer' => 'keyword_mapping_v2',
                'source_tag' => 'gap',
                'competitor_count' => count($competitors),
            ),
        );
        $response = $this->aysa_keyword_v2_finalize_snapshot_contract($project_id, 'competitor_gap_v2', $response, 'rows');
        $this->aysa_keyword_v2_cache_set($project_id, 'competitor_gap_v2', $response);
        wp_send_json_success($response);
    }

    // LEGACY WORKSPACE ENDPOINT: do not use as the canonical Agent8 V3 Keyword Mapping rerun.
    // Agent8 V3 research must go through start_keyword_research_run -> KeywordResearchRunService -> plugin projection sync.
    public function handle_keyword_mapping_v1_workspace() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_keyword_cache_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        $force_refresh = !empty($_POST['force_refresh']);
        if (!$force_refresh) {
            $cached = $this->aysa_keyword_v2_cache_get($project_id, 'keyword_mapping_v2');
            if (is_array($cached)) {
                wp_send_json_success($cached);
                return;
            }
        }

        $payload = array(
            'project_id' => $project_id,
            'raw_context' => $this->aysa_collect_research_context_bundle($project_id),
            'force_refresh' => $force_refresh ? 1 : 0,
        );
        $result = $this->aysa_broker_request('POST', '/v1/research/workspaces/keyword-mapping', $payload, 240);
        if (is_wp_error($result)) {
            wp_send_json_error($result->get_error_message());
            return;
        }
        if (is_array($result) && isset($result['data']) && is_array($result['data'])) {
            $result = $result['data'];
        }
        if (!is_array($result) || empty($result['ok'])) {
            $message = is_array($result) && !empty($result['error']) ? (string) $result['error'] : 'Keyword Mapping workspace failed.';
            wp_send_json_error($message);
            return;
        }

        if (!isset($result['cache_meta']) || !is_array($result['cache_meta'])) {
            $result['cache_meta'] = array();
        }
        $result['cache_meta']['source'] = 'live_refresh';
        $result['cache_meta']['cached_at'] = gmdate('c');
        $result = $this->aysa_keyword_v2_finalize_snapshot_contract($project_id, 'keyword_mapping_v2', $result, 'clusters');
        $this->aysa_keyword_v2_cache_set($project_id, 'keyword_mapping_v2', $result);
        // LEGACY PROJECTION SYNC: this writes operational local associations only; it is not the source of truth.
        if (!empty($_POST['sync_local_projection'])) {
            $result['local_projection_sync'] = $this->aysa_keyword_mapping_sync_local_associations_from_workspace($project_id, $result);
        } else {
            $result['local_projection_sync'] = array(
                'ok' => true,
                'source' => 'keyword_mapping_v2',
                'status' => 'skipped',
                'reason' => 'explicit_sync_required',
            );
        }
        wp_send_json_success($result);
    }

    public function handle_keyword_mapping_v1_save_selection() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_keyword_cache_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        $payload = array(
            'project_id' => $project_id,
            'cluster_id' => sanitize_text_field((string) wp_unslash($_POST['cluster_id'] ?? '')),
            'keyword' => sanitize_text_field((string) wp_unslash($_POST['keyword'] ?? '')),
            'selection_state' => sanitize_key((string) wp_unslash($_POST['selection_state'] ?? 'review')),
        );

        $result = $this->aysa_broker_request('POST', '/v1/research/workspaces/save-selection', $payload, 60);
        if (is_wp_error($result)) {
            wp_send_json_error($result->get_error_message());
            return;
        }
        if (is_array($result) && isset($result['data']) && is_array($result['data'])) {
            $result = $result['data'];
        }
        if (!is_array($result) || empty($result['ok'])) {
            $message = is_array($result) && !empty($result['error']) ? (string) $result['error'] : 'Keyword Mapping selection save failed.';
            wp_send_json_error($message);
            return;
        }

        $this->aysa_keyword_v2_cache_delete($project_id, 'keyword_mapping_v2');
        $this->aysa_keyword_v2_cache_delete($project_id, 'missing_pages_v2');
        $this->aysa_keyword_v2_cache_delete($project_id, 'content_strategy_v2');

        wp_send_json_success($result);
    }

    public function handle_missing_pages_v1_workspace() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_keyword_cache_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }

        $force_refresh = !empty($_POST['force_refresh']);
        if (!$force_refresh) {
            $cached = $this->aysa_keyword_v2_cache_get($project_id, 'missing_pages_v2');
            if (is_array($cached)) {
                wp_send_json_success($cached);
                return;
            }
        }

        $payload = array(
            'project_id' => $project_id,
            'raw_context' => $this->aysa_collect_research_context_bundle($project_id),
            'force_refresh' => $force_refresh ? 1 : 0,
        );
        $result = $this->aysa_broker_request('POST', '/v1/research/workspaces/missing-pages', $payload, 240);
        if (is_wp_error($result)) {
            wp_send_json_error($result->get_error_message());
            return;
        }
        if (is_array($result) && isset($result['data']) && is_array($result['data'])) {
            $result = $result['data'];
        }
        if (!is_array($result) || empty($result['ok'])) {
            $message = is_array($result) && !empty($result['error']) ? (string) $result['error'] : 'Missing Pages workspace failed.';
            wp_send_json_error($message);
            return;
        }

        $result['cache_meta'] = isset($result['cache_meta']) && is_array($result['cache_meta']) ? $result['cache_meta'] : array();
        $result['cache_meta']['source'] = 'live_refresh';
        $result['cache_meta']['cached_at'] = gmdate('c');
        $result = $this->aysa_keyword_v2_finalize_snapshot_contract($project_id, 'missing_pages_v2', $result, 'groups');
        $this->aysa_keyword_v2_cache_set($project_id, 'missing_pages_v2', $result);
        wp_send_json_success($result);
    }

    public function handle_content_strategy_v1_workspace() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_keyword_cache_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        $site_host = wp_parse_url(home_url('/'), PHP_URL_HOST);
        $site_host = is_string($site_host) ? strtolower(trim((string) preg_replace('/^www\./i', '', $site_host))) : '';
        $canonical_project_id = intval($this->aysa_resolve_agent8_canonical_project_id($project_id, $site_host, array()));
        $workspace_project_id = $canonical_project_id > 0 ? $canonical_project_id : $project_id;

        $force_refresh = !empty($_POST['force_refresh']);
        if (!$force_refresh) {
            $cached = $this->aysa_keyword_v2_cache_get($project_id, 'content_strategy_v2');
            if (is_array($cached)) {
                wp_send_json_success($cached);
                return;
            }
        }

        $payload = array(
            'project_id' => $workspace_project_id,
            'canonical_project_id' => $canonical_project_id,
            'wp_project_id' => $project_id,
            'raw_context' => $this->aysa_collect_research_context_bundle($project_id),
            'force_refresh' => $force_refresh ? 1 : 0,
        );
        $result = $this->aysa_broker_request('POST', '/v1/research/workspaces/content-strategy', $payload, 240);
        if (is_wp_error($result)) {
            wp_send_json_error($result->get_error_message());
            return;
        }
        if (is_array($result) && isset($result['data']) && is_array($result['data'])) {
            $result = $result['data'];
        }
        if (!is_array($result) || empty($result['ok'])) {
            $message = is_array($result) && !empty($result['error']) ? (string) $result['error'] : 'Content Strategy workspace failed.';
            wp_send_json_error($message);
            return;
        }

        $result['cache_meta'] = isset($result['cache_meta']) && is_array($result['cache_meta']) ? $result['cache_meta'] : array();
        $result['cache_meta']['source'] = 'live_refresh';
        $result['cache_meta']['cached_at'] = gmdate('c');
        $this->aysa_keyword_v2_cache_set($project_id, 'content_strategy_v2', $result);
        wp_send_json_success($result);
    }

    private function aysa_keyword_mapping_v1_get_cached_workspace($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return null;
        }

        $cached = $this->aysa_keyword_v2_cache_get($project_id, 'keyword_mapping_v2');
        if (is_array($cached) && !empty($cached['ok']) && !empty($cached['clusters']) && is_array($cached['clusters'])) {
            return $cached;
        }

        return null;
    }

    private function aysa_adjustments_extract_keyword_mapping_cluster_payload($project_id) {
        $workspace = $this->aysa_keyword_mapping_v1_get_cached_workspace($project_id);
        if (!is_array($workspace) || empty($workspace['clusters']) || !is_array($workspace['clusters'])) {
            return null;
        }

        $selected = array();
        $review = array();
        $fallback = array();
        foreach ($workspace['clusters'] as $cluster) {
            if (!is_array($cluster)) {
                continue;
            }
            $state = sanitize_key((string) ($cluster['selection_state'] ?? 'review'));
            if ($state === 'excluded') {
                continue;
            }
            if ($state === 'selected') {
                $selected[] = $cluster;
            } elseif ($state === 'review') {
                $review[] = $cluster;
            } else {
                $fallback[] = $cluster;
            }
        }

        $clusters = !empty($selected) ? $selected : (!empty($review) ? $review : $fallback);
        if (empty($clusters)) {
            return null;
        }

        usort($clusters, static function ($left, $right) {
            return ((float) ($right['priority_score'] ?? 0.0)) <=> ((float) ($left['priority_score'] ?? 0.0));
        });
        $clusters = array_slice($clusters, 0, 8);

        $clusterGroups = array();
        $keywords = array();
        $gapKeywords = array();
        $mappedKeywords = array();
        $notMappedKeywords = array();
        $transactionalKeywords = array();
        $informationalKeywords = array();
        $localKeywords = array();
        $primaryKeyword = '';

        foreach ($clusters as $cluster) {
            $primary = isset($cluster['primary_keyword']) && is_array($cluster['primary_keyword']) ? $cluster['primary_keyword'] : array();
            $secondaryRows = isset($cluster['secondary_keywords']) && is_array($cluster['secondary_keywords']) ? $cluster['secondary_keywords'] : array();

            $clusterKeywords = array();
            $primaryText = sanitize_text_field((string) ($primary['keyword'] ?? ''));
            if ($primaryText !== '') {
                $clusterKeywords[] = $primaryText;
                if ($primaryKeyword === '') {
                    $primaryKeyword = $primaryText;
                }
            }

            foreach ($secondaryRows as $secondaryRow) {
                if (!is_array($secondaryRow)) {
                    continue;
                }
                $secondaryText = sanitize_text_field((string) ($secondaryRow['keyword'] ?? ''));
                if ($secondaryText === '' || in_array($secondaryText, $clusterKeywords, true)) {
                    continue;
                }
                $clusterKeywords[] = $secondaryText;
            }

            if (empty($clusterKeywords)) {
                continue;
            }

            foreach ($clusterKeywords as $keyword) {
                if (!in_array($keyword, $keywords, true)) {
                    $keywords[] = $keyword;
                }
            }

            $clusterSources = isset($cluster['sources']) && is_array($cluster['sources']) ? $cluster['sources'] : array();
            $mappingStatus = sanitize_key((string) ($cluster['mapping_status'] ?? 'not_mapped'));
            $intentSummary = sanitize_key((string) ($cluster['intent_summary'] ?? ($primary['intent'] ?? 'review')));
            foreach ($clusterKeywords as $keyword) {
                if (in_array('GAP', $clusterSources, true) && !in_array($keyword, $gapKeywords, true)) {
                    $gapKeywords[] = $keyword;
                }
                if ($mappingStatus === 'mapped' && !in_array($keyword, $mappedKeywords, true)) {
                    $mappedKeywords[] = $keyword;
                }
                if ($mappingStatus !== 'mapped' && !in_array($keyword, $notMappedKeywords, true)) {
                    $notMappedKeywords[] = $keyword;
                }
                if (in_array($intentSummary, array('transactional', 'commercial'), true) && !in_array($keyword, $transactionalKeywords, true)) {
                    $transactionalKeywords[] = $keyword;
                }
                if ($intentSummary === 'informational' && !in_array($keyword, $informationalKeywords, true)) {
                    $informationalKeywords[] = $keyword;
                }
                if ($intentSummary === 'local' && !in_array($keyword, $localKeywords, true)) {
                    $localKeywords[] = $keyword;
                }
            }

            $clusterGroups[] = array(
                'cluster_id' => sanitize_text_field((string) ($cluster['cluster_id'] ?? '')),
                'cluster_name' => sanitize_text_field((string) ($cluster['label'] ?? $primaryText)),
                'primary_keyword' => $primaryText,
                'target_keywords' => $clusterKeywords,
                'cluster_keywords' => $clusterKeywords,
                'intent' => sanitize_key((string) ($cluster['intent_summary'] ?? ($primary['intent'] ?? 'review'))),
                'mapping_status' => sanitize_key((string) ($cluster['mapping_status'] ?? 'not_mapped')),
                'mapping_confidence' => round((float) ($cluster['mapping_confidence'] ?? 0.0), 3),
                'target_page_type' => sanitize_key((string) ($cluster['target_page_type'] ?? 'page')),
                'business_fit_score' => round((float) ($cluster['business_fit_score'] ?? ($primary['business_fit_score'] ?? 0.0)), 3),
                'matched_page' => $this->aysa_adjustments_sanitize_match_payload($cluster['matched_page'] ?? null),
                'review_reason' => sanitize_text_field((string) ($cluster['review_context']['reason'] ?? ($cluster['explainability']['why_mapped'] ?? ''))),
                'review_context' => $this->aysa_adjustments_sanitize_review_context($cluster['review_context'] ?? array()),
                'page_matches' => $this->aysa_adjustments_sanitize_page_matches($cluster['explainability']['page_matches'] ?? array()),
                'priority_score' => round((float) ($cluster['priority_score'] ?? 0.0), 2),
                'sources' => isset($cluster['sources']) && is_array($cluster['sources']) ? array_values(array_filter(array_map('sanitize_text_field', $cluster['sources']))) : array(),
                'selection_state' => sanitize_key((string) ($cluster['selection_state'] ?? 'review')),
            );
        }

        if (empty($clusterGroups)) {
            return null;
        }

        return array(
            'primary_keyword' => $primaryKeyword !== '' ? $primaryKeyword : (string) ($keywords[0] ?? ''),
            'keywords' => array_slice($keywords, 0, 24),
            'gap_keywords' => array_slice($gapKeywords, 0, 24),
            'mapped_keywords' => array_slice($mappedKeywords, 0, 24),
            'not_mapped_keywords' => array_slice($notMappedKeywords, 0, 24),
            'transactional_keywords' => array_slice($transactionalKeywords, 0, 24),
            'informational_keywords' => array_slice($informationalKeywords, 0, 24),
            'local_keywords' => array_slice($localKeywords, 0, 24),
            'cluster_groups' => $clusterGroups,
            'cluster_count' => count($clusterGroups),
        );
    }

    private function aysa_adjustments_sanitize_match_payload($match) {
        if (!is_array($match)) {
            return null;
        }

        $url = esc_url_raw((string) ($match['url'] ?? ''));
        if ($url === '') {
            return null;
        }

        return array(
            'url' => $url,
            'title' => sanitize_text_field((string) ($match['title'] ?? '')),
            'type' => sanitize_key((string) ($match['type'] ?? '')),
            'taxonomy' => sanitize_key((string) ($match['taxonomy'] ?? '')),
            'confidence' => round((float) ($match['confidence'] ?? 0.0), 3),
            'primary_coverage' => round((float) ($match['primary_coverage'] ?? 0.0), 3),
            'modifier_coverage' => round((float) ($match['modifier_coverage'] ?? 0.0), 3),
        );
    }

    private function aysa_adjustments_sanitize_page_matches($matches) {
        $matches = is_array($matches) ? $matches : array();
        $sanitized = array();
        foreach (array_slice($matches, 0, 3) as $match) {
            $payload = $this->aysa_adjustments_sanitize_match_payload($match);
            if ($payload !== null) {
                $sanitized[] = $payload;
            }
        }

        return $sanitized;
    }

    private function aysa_adjustments_sanitize_review_context($context) {
        $context = is_array($context) ? $context : array();

        return array(
            'reason' => sanitize_text_field((string) ($context['reason'] ?? '')),
            'target_page_type' => sanitize_key((string) ($context['target_page_type'] ?? '')),
            'business_fit_score' => round((float) ($context['business_fit_score'] ?? 0.0), 3),
            'mapping_threshold' => round((float) ($context['mapping_threshold'] ?? 0.0), 3),
            'review_floor' => round((float) ($context['review_floor'] ?? 0.0), 3),
            'failed_checks' => array_values(array_filter(array_map('sanitize_key', is_array($context['failed_checks'] ?? null) ? $context['failed_checks'] : array()))),
            'top_candidates' => $this->aysa_adjustments_sanitize_page_matches($context['top_candidates'] ?? array()),
        );
    }

    private function aysa_adjustments_prepare_persona_cluster_groups($cluster_groups) {
        $cluster_groups = is_array($cluster_groups) ? $cluster_groups : array();
        if (empty($cluster_groups)) {
            return array();
        }

        $preferred = array();
        $secondary = array();
        foreach ($cluster_groups as $group) {
            if (!is_array($group)) {
                continue;
            }
            $keywords = array_values(array_filter(array_map('sanitize_text_field', (array) ($group['cluster_keywords'] ?? $group['target_keywords'] ?? array()))));
            if (empty($keywords)) {
                continue;
            }

            $intent = sanitize_key((string) ($group['intent'] ?? 'review'));
            if (in_array($intent, array('transactional', 'commercial', 'local'), true)) {
                $preferred[] = $group;
            } elseif ($intent === 'informational') {
                $secondary[] = $group;
            }
        }

        $ordered = array_merge($preferred, $secondary);
        usort($ordered, static function ($left, $right) {
            return ((float) ($right['priority_score'] ?? 0.0)) <=> ((float) ($left['priority_score'] ?? 0.0));
        });

        return array_slice($ordered, 0, 6);
    }

    private function aysa_adjustments_build_persona_scenarios($cluster_groups, $payload) {
        $cluster_groups = is_array($cluster_groups) ? $cluster_groups : array();
        $payload = is_array($payload) ? $payload : array();
        if (empty($cluster_groups)) {
            return array();
        }

        $city = sanitize_text_field((string) ($payload['city'] ?? ''));
        $business_name = sanitize_text_field((string) ($payload['business_name'] ?? ''));
        $industry = sanitize_text_field((string) ($payload['industry'] ?? ''));
        $scenarios = array();

        foreach ($cluster_groups as $group) {
            if (!is_array($group)) {
                continue;
            }

            $intent = sanitize_key((string) ($group['intent'] ?? 'informational'));
            $cluster_name = sanitize_text_field((string) ($group['cluster_name'] ?? ''));
            $primary_keyword = sanitize_text_field((string) ($group['primary_keyword'] ?? $cluster_name));
            $mapping_status = sanitize_key((string) ($group['mapping_status'] ?? 'not_mapped'));
            $target_page_type = sanitize_key((string) ($group['target_page_type'] ?? 'page'));
            $keyword_list = array_values(array_filter(array_map('sanitize_text_field', (array) ($group['target_keywords'] ?? $group['cluster_keywords'] ?? array()))));

            $persona = 'buyer researching options before ordering';
            $scenario = 'The user is evaluating options and wants to understand what best fits their need before making a purchase.';
            $pain_point = 'They are unsure which option is relevant and do not want to make the wrong choice.';
            $desired_outcome = 'Choose the right option faster and move confidently toward an order.';

            if ($intent === 'local') {
                $persona = $city !== ''
                    ? 'local buyer looking for a reliable option in ' . $city
                    : 'local buyer looking for a nearby reliable option';
                $scenario = $city !== ''
                    ? 'The user needs ' . $primary_keyword . ' in ' . $city . ' and wants a convenient, trustworthy local choice.'
                    : 'The user wants a relevant nearby option and needs local confidence before ordering.';
                $pain_point = 'They do not know which provider is nearby, reliable, and worth trusting.';
                $desired_outcome = 'Find the right local option quickly and order with confidence.';
            } elseif (in_array($intent, array('transactional', 'commercial'), true)) {
                $persona = 'buyer comparing options before placing an online order';
                $scenario = 'The user is close to purchase and is comparing ' . $primary_keyword . ' to decide what to order now.';
                if ($mapping_status !== 'mapped' || in_array($target_page_type, array('landing_page', 'category', 'service'), true)) {
                    $pain_point = 'They cannot immediately tell which option or page best matches what they want to buy.';
                    $desired_outcome = 'Understand the best-fit option fast and complete the purchase with less hesitation.';
                } else {
                    $pain_point = 'They want reassurance around fit, price, trust, or delivery before ordering.';
                    $desired_outcome = 'Choose confidently and move from comparison to purchase.';
                }
            } elseif ($intent === 'informational') {
                $persona = 'researching customer who wants to make a better buying decision';
                $scenario = 'The user is researching ' . $primary_keyword . ' before deciding what to buy from ' . ($business_name !== '' ? $business_name : ($industry !== '' ? $industry : 'the business')) . '.';
                $pain_point = 'They need practical guidance, not generic explanations, before taking the next step.';
                $desired_outcome = 'Get useful clarity that leads to a better purchase decision.';
            }

            $scenarios[] = array(
                'cluster_id' => sanitize_text_field((string) ($group['cluster_id'] ?? '')),
                'cluster_name' => $cluster_name !== '' ? $cluster_name : $primary_keyword,
                'primary_keyword' => $primary_keyword,
                'intent' => $intent !== '' ? $intent : 'informational',
                'mapping_status' => $mapping_status !== '' ? $mapping_status : 'not_mapped',
                'target_page_type' => $target_page_type !== '' ? $target_page_type : 'page',
                'persona' => $persona,
                'scenario' => $scenario,
                'pain_point' => $pain_point,
                'desired_outcome' => $desired_outcome,
                'target_keywords' => array_slice($keyword_list, 0, 6),
            );
        }

        return $scenarios;
    }

    private function aysa_adjustments_prepare_local_seo_cluster_groups($cluster_groups) {
        $cluster_groups = is_array($cluster_groups) ? $cluster_groups : array();
        if (empty($cluster_groups)) {
            return array();
        }

        $preferred = array();
        $secondary = array();
        foreach ($cluster_groups as $group) {
            if (!is_array($group)) {
                continue;
            }
            $keywords = array_values(array_filter(array_map('sanitize_text_field', (array) ($group['cluster_keywords'] ?? $group['target_keywords'] ?? array()))));
            if (empty($keywords)) {
                continue;
            }

            $intent = sanitize_key((string) ($group['intent'] ?? 'review'));
            if ($intent === 'local') {
                $preferred[] = $group;
            } elseif (in_array($intent, array('transactional', 'commercial'), true)) {
                $secondary[] = $group;
            }
        }

        $ordered = array_merge($preferred, $secondary);
        usort($ordered, static function ($left, $right) {
            return ((float) ($right['priority_score'] ?? 0.0)) <=> ((float) ($left['priority_score'] ?? 0.0));
        });

        return array_slice($ordered, 0, 6);
    }

    private function aysa_adjustments_build_local_context($cluster_groups, $payload) {
        $cluster_groups = is_array($cluster_groups) ? $cluster_groups : array();
        $payload = is_array($payload) ? $payload : array();

        $city = sanitize_text_field((string) ($payload['city'] ?? ''));
        $country = sanitize_text_field((string) ($payload['country'] ?? ''));
        $region = sanitize_text_field((string) ($payload['target_region'] ?? ''));
        $business_name = sanitize_text_field((string) ($payload['business_name'] ?? ''));
        $neighborhoods = array_values(array_filter(array_map('sanitize_text_field', (array) ($payload['neighborhoods'] ?? array()))));

        $cluster_contexts = array();
        foreach ($cluster_groups as $group) {
            if (!is_array($group)) {
                continue;
            }

            $cluster_name = sanitize_text_field((string) ($group['cluster_name'] ?? ''));
            $primary_keyword = sanitize_text_field((string) ($group['primary_keyword'] ?? $cluster_name));
            $intent = sanitize_key((string) ($group['intent'] ?? 'local'));
            $mapping_status = sanitize_key((string) ($group['mapping_status'] ?? 'not_mapped'));

            $search_scenario = $city !== ''
                ? 'The user wants ' . $primary_keyword . ' in ' . $city . ' and needs a trustworthy nearby option.'
                : 'The user is searching for a relevant local option and needs nearby confidence before ordering.';
            $local_friction = 'They are unsure which provider actually serves their area well and can be trusted locally.';
            $desired_outcome = 'Find the best nearby option quickly and order with confidence.';

            if ($mapping_status === 'mapped') {
                $local_friction = 'They need reassurance that this local option is the right fit compared with alternatives nearby.';
                $desired_outcome = 'Choose the existing local option confidently and convert faster.';
            } elseif (in_array($intent, array('transactional', 'commercial'), true)) {
                $local_friction = 'They are close to buying but need clarity on local fit, delivery area, or convenience.';
                $desired_outcome = 'Understand local relevance quickly and move to purchase.';
            }

            $cluster_contexts[] = array(
                'cluster_id' => sanitize_text_field((string) ($group['cluster_id'] ?? '')),
                'cluster_name' => $cluster_name !== '' ? $cluster_name : $primary_keyword,
                'primary_keyword' => $primary_keyword,
                'intent' => $intent !== '' ? $intent : 'local',
                'search_scenario' => $search_scenario,
                'local_friction' => $local_friction,
                'desired_outcome' => $desired_outcome,
                'target_keywords' => array_values(array_filter(array_map('sanitize_text_field', (array) ($group['target_keywords'] ?? array())))),
            );
        }

        return array(
            'city' => $city,
            'region' => $region,
            'country' => $country,
            'business_name' => $business_name,
            'neighborhoods' => $neighborhoods,
            'cluster_contexts' => $cluster_contexts,
        );
    }

    private function aysa_adjustments_prepare_authority_cluster_groups($cluster_groups) {
        $cluster_groups = is_array($cluster_groups) ? $cluster_groups : array();
        if (empty($cluster_groups)) {
            return array();
        }

        $preferred = array();
        $secondary = array();
        foreach ($cluster_groups as $group) {
            if (!is_array($group)) {
                continue;
            }
            $keywords = array_values(array_filter(array_map('sanitize_text_field', (array) ($group['cluster_keywords'] ?? $group['target_keywords'] ?? array()))));
            if (empty($keywords)) {
                continue;
            }

            $intent = sanitize_key((string) ($group['intent'] ?? 'review'));
            $mapping_status = sanitize_key((string) ($group['mapping_status'] ?? 'not_mapped'));
            if (in_array($intent, array('commercial', 'transactional', 'informational'), true)) {
                if ($mapping_status === 'mapped') {
                    $preferred[] = $group;
                } else {
                    $secondary[] = $group;
                }
            }
        }

        $ordered = array_merge($preferred, $secondary);
        usort($ordered, static function ($left, $right) {
            return ((float) ($right['priority_score'] ?? 0.0)) <=> ((float) ($left['priority_score'] ?? 0.0));
        });

        return array_slice($ordered, 0, 6);
    }

    private function aysa_adjustments_build_authority_context($cluster_groups, $payload) {
        $cluster_groups = is_array($cluster_groups) ? $cluster_groups : array();
        $payload = is_array($payload) ? $payload : array();
        if (empty($cluster_groups)) {
            return array();
        }

        $business_name = sanitize_text_field((string) ($payload['business_name'] ?? ''));
        $industry = sanitize_text_field((string) ($payload['industry'] ?? ''));
        $company_info = sanitize_text_field((string) ($payload['company_info'] ?? ''));
        $city = sanitize_text_field((string) ($payload['city'] ?? ''));
        $contexts = array();

        foreach ($cluster_groups as $group) {
            if (!is_array($group)) {
                continue;
            }

            $cluster_name = sanitize_text_field((string) ($group['cluster_name'] ?? ''));
            $primary_keyword = sanitize_text_field((string) ($group['primary_keyword'] ?? $cluster_name));
            $intent = sanitize_key((string) ($group['intent'] ?? 'informational'));
            $mapping_status = sanitize_key((string) ($group['mapping_status'] ?? 'not_mapped'));
            $page_type = sanitize_key((string) ($group['target_page_type'] ?? 'page'));

            $authority_goal = 'Give the reader concrete confidence through useful expertise and real decision support.';
            $trust_signal = 'practical expertise';
            $angle_suggestions = array('expert explanation', 'buying guidance');

            if ($mapping_status === 'mapped') {
                $authority_goal = 'Strengthen trust around an existing commercial page by adding expertise, reassurance, and comparison value.';
                $trust_signal = 'experience-backed guidance';
                $angle_suggestions = array('case study', 'review', 'comparison');
            } elseif (in_array($intent, array('commercial', 'transactional'), true)) {
                $authority_goal = 'Help the reader evaluate options more confidently before buying.';
                $trust_signal = 'decision confidence';
                $angle_suggestions = array('comparison', 'quality framework', 'buying guidance');
            } elseif ($intent === 'informational') {
                $authority_goal = 'Demonstrate real expertise with practical, experience-backed explanations.';
                $trust_signal = 'expert explanation';
                $angle_suggestions = array('expert explanation', 'practical review', 'experience-backed advice');
            }

            $contexts[] = array(
                'cluster_id' => sanitize_text_field((string) ($group['cluster_id'] ?? '')),
                'cluster_name' => $cluster_name !== '' ? $cluster_name : $primary_keyword,
                'primary_keyword' => $primary_keyword,
                'intent' => $intent !== '' ? $intent : 'informational',
                'mapping_status' => $mapping_status !== '' ? $mapping_status : 'not_mapped',
                'target_page_type' => $page_type !== '' ? $page_type : 'page',
                'authority_goal' => $authority_goal,
                'trust_signal' => $trust_signal,
                'angle_suggestions' => $angle_suggestions,
                'business_name' => $business_name,
                'industry' => $industry,
                'company_info' => $company_info,
                'city' => $city,
                'target_keywords' => array_values(array_filter(array_map('sanitize_text_field', (array) ($group['target_keywords'] ?? array())))),
            );
        }

        return array(
            'business_name' => $business_name,
            'industry' => $industry,
            'company_info' => $company_info,
            'city' => $city,
            'cluster_contexts' => $contexts,
        );
    }

    private function aysa_adjustments_build_customer_journey_context($cluster_groups, $payload) {
        $cluster_groups = is_array($cluster_groups) ? $cluster_groups : array();
        $payload = is_array($payload) ? $payload : array();
        if (empty($cluster_groups)) {
            return array();
        }

        $business_name = sanitize_text_field((string) ($payload['business_name'] ?? ''));
        $city = sanitize_text_field((string) ($payload['city'] ?? ''));
        $journey_rows = array();

        foreach ($cluster_groups as $group) {
            if (!is_array($group)) {
                continue;
            }

            $cluster_name = sanitize_text_field((string) ($group['cluster_name'] ?? ''));
            $primary_keyword = sanitize_text_field((string) ($group['primary_keyword'] ?? $cluster_name));
            $intent = sanitize_key((string) ($group['intent'] ?? 'informational'));
            $mapping_status = sanitize_key((string) ($group['mapping_status'] ?? 'not_mapped'));

            $awareness = 'The user is early in the journey, notices a problem or need, and is unsure what the right direction is.';
            $consideration = 'The user is evaluating options, comparisons, and trade-offs to understand what solution fits best.';
            $decision = 'The user is ready to choose a provider and needs trust, clarity on expectations, and confidence to buy.';

            if (in_array($intent, array('commercial', 'transactional'), true)) {
                $awareness = 'The user knows they need something related to ' . $primary_keyword . ' but is still clarifying what is actually appropriate.';
                $consideration = 'The user is comparing options for ' . $primary_keyword . ' and wants to understand differences before buying.';
                $decision = 'The user is close to purchase and wants confidence around provider choice, fit, and ordering expectations.';
            } elseif ($intent === 'local') {
                $awareness = 'The user realizes they need a local option' . ($city !== '' ? ' in ' . $city : '') . ' but is not yet sure what to look for.';
                $consideration = 'The user is comparing local options and wants to understand which one fits the need best.';
                $decision = 'The user wants to choose the right local provider with minimal risk and high confidence.';
            }

            $journey_rows[] = array(
                'cluster_id' => sanitize_text_field((string) ($group['cluster_id'] ?? '')),
                'cluster_name' => $cluster_name !== '' ? $cluster_name : $primary_keyword,
                'primary_keyword' => $primary_keyword,
                'intent' => $intent !== '' ? $intent : 'informational',
                'mapping_status' => $mapping_status !== '' ? $mapping_status : 'not_mapped',
                'business_name' => $business_name,
                'city' => $city,
                'awareness_context' => $awareness,
                'consideration_context' => $consideration,
                'decision_context' => $decision,
                'target_keywords' => array_values(array_filter(array_map('sanitize_text_field', (array) ($group['target_keywords'] ?? array())))),
            );
        }

        return array(
            'business_name' => $business_name,
            'city' => $city,
            'cluster_contexts' => $journey_rows,
        );
    }

    private function aysa_adjustments_build_evergreen_fresh_context($cluster_groups, $payload) {
        $cluster_groups = is_array($cluster_groups) ? $cluster_groups : array();
        $payload = is_array($payload) ? $payload : array();
        if (empty($cluster_groups)) {
            return array();
        }

        $business_name = sanitize_text_field((string) ($payload['business_name'] ?? ''));
        $industry = sanitize_text_field((string) ($payload['industry'] ?? ''));
        $city = sanitize_text_field((string) ($payload['city'] ?? ''));
        $company_info = sanitize_text_field((string) ($payload['company_info'] ?? ''));
        $contexts = array();

        foreach ($cluster_groups as $group) {
            if (!is_array($group)) {
                continue;
            }

            $cluster_name = sanitize_text_field((string) ($group['cluster_name'] ?? ''));
            $primary_keyword = sanitize_text_field((string) ($group['primary_keyword'] ?? $cluster_name));
            $intent = sanitize_key((string) ($group['intent'] ?? 'informational'));
            $mapping_status = sanitize_key((string) ($group['mapping_status'] ?? 'not_mapped'));

            $evergreen_reason = 'This topic solves a stable problem or supports a long-term decision.';
            $fresh_reason = 'This topic can be made timely through seasonality, trends, recent changes, or current urgency.';
            if (in_array($intent, array('commercial', 'transactional'), true)) {
                $evergreen_reason = 'This topic supports recurring buying decisions and remains commercially relevant over time.';
                $fresh_reason = 'This topic can be refreshed through current offers, seasonal demand, or timely comparison needs.';
            }

            $contexts[] = array(
                'cluster_id' => sanitize_text_field((string) ($group['cluster_id'] ?? '')),
                'cluster_name' => $cluster_name !== '' ? $cluster_name : $primary_keyword,
                'primary_keyword' => $primary_keyword,
                'intent' => $intent !== '' ? $intent : 'informational',
                'mapping_status' => $mapping_status !== '' ? $mapping_status : 'not_mapped',
                'evergreen_reason' => $evergreen_reason,
                'fresh_reason' => $fresh_reason,
                'target_keywords' => array_values(array_filter(array_map('sanitize_text_field', (array) ($group['target_keywords'] ?? array())))),
            );
        }

        return array(
            'business_name' => $business_name,
            'industry' => $industry,
            'city' => $city,
            'company_info' => $company_info,
            'cluster_contexts' => $contexts,
        );
    }

    private function aysa_adjustments_prepare_competitor_cluster_groups($cluster_groups) {
        $cluster_groups = is_array($cluster_groups) ? $cluster_groups : array();
        if (empty($cluster_groups)) {
            return array();
        }

        $prepared = array();
        foreach ($cluster_groups as $group) {
            if (!is_array($group)) {
                continue;
            }

            $keywords = array_values(array_filter(array_map('sanitize_text_field', (array) ($group['cluster_keywords'] ?? $group['target_keywords'] ?? array()))));
            if (empty($keywords)) {
                continue;
            }

            $sources = array_values(array_filter(array_map('sanitize_text_field', (array) ($group['sources'] ?? array()))));
            $mapping_status = sanitize_key((string) ($group['mapping_status'] ?? 'not_mapped'));
            $intent = sanitize_key((string) ($group['intent'] ?? 'informational'));
            $score = (float) ($group['priority_score'] ?? 0.0);

            if (in_array('GAP', $sources, true)) {
                $score += 40.0;
            }
            if ($mapping_status !== 'mapped') {
                $score += 20.0;
            }
            if (in_array($intent, array('transactional', 'commercial', 'local'), true)) {
                $score += 10.0;
            } elseif ($intent === 'informational') {
                $score += 3.0;
            }

            $group['_competitor_score'] = $score;
            $prepared[] = $group;
        }

        usort($prepared, static function ($left, $right) {
            return ((float) ($right['_competitor_score'] ?? 0.0)) <=> ((float) ($left['_competitor_score'] ?? 0.0));
        });

        $prepared = array_slice($prepared, 0, 6);
        foreach ($prepared as &$group) {
            unset($group['_competitor_score']);
        }
        unset($group);

        return $prepared;
    }

    private function aysa_adjustments_build_competitor_title_signals($project_id, $cluster_groups, $payload, $competitor_domains) {
        $project_id = intval($project_id);
        $cluster_groups = is_array($cluster_groups) ? $cluster_groups : array();
        $payload = is_array($payload) ? $payload : array();
        $competitor_domains = array_values(array_filter(array_map('sanitize_text_field', (array) $competitor_domains)));

        $cluster_map = array();
        foreach ($cluster_groups as $group) {
            if (!is_array($group)) {
                continue;
            }

            $cluster_id = sanitize_text_field((string) ($group['cluster_id'] ?? ''));
            $cluster_name = sanitize_text_field((string) ($group['cluster_name'] ?? ''));
            $primary_keyword = sanitize_text_field((string) ($group['primary_keyword'] ?? $cluster_name));
            $keywords = array_values(array_filter(array_map('sanitize_text_field', (array) ($group['target_keywords'] ?? $group['cluster_keywords'] ?? array()))));
            if ($primary_keyword === '' && !empty($keywords)) {
                $primary_keyword = (string) $keywords[0];
            }

            foreach ($keywords as $keyword) {
                $norm = $this->aysa_keyword_strategy_normalize_keyword($keyword);
                if ($norm === '' || isset($cluster_map[$norm])) {
                    continue;
                }
                $cluster_map[$norm] = array(
                    'cluster_id' => $cluster_id,
                    'cluster_name' => $cluster_name !== '' ? $cluster_name : $primary_keyword,
                    'primary_keyword' => $primary_keyword !== '' ? $primary_keyword : $keyword,
                    'target_keywords' => array_slice($keywords, 0, 6),
                    'intent' => sanitize_key((string) ($group['intent'] ?? 'informational')),
                    'mapping_status' => sanitize_key((string) ($group['mapping_status'] ?? 'not_mapped')),
                );
            }
        }

        $signals = array();
        $seen = array();
        $gap_snapshot = $this->aysa_keyword_v2_cache_get($project_id, 'competitor_gap_v2');
        $gap_rows = is_array($gap_snapshot) && !empty($gap_snapshot['rows']) && is_array($gap_snapshot['rows'])
            ? $gap_snapshot['rows']
            : array();

        foreach ($gap_rows as $row) {
            if (!is_array($row)) {
                continue;
            }

            $keyword = sanitize_text_field((string) ($row['keyword'] ?? ''));
            if ($keyword === '') {
                continue;
            }

            $norm = $this->aysa_keyword_strategy_normalize_keyword($keyword);
            $cluster = $cluster_map[$norm] ?? null;
            if (!is_array($cluster)) {
                continue;
            }

            $row_competitors = array();
            $raw_competitors = $row['competitors'] ?? array();
            if (is_string($raw_competitors)) {
                $decoded = json_decode($raw_competitors, true);
                $raw_competitors = is_array($decoded) ? $decoded : preg_split('/\s*,\s*/', $raw_competitors);
            }
            foreach ((array) $raw_competitors as $value) {
                $value = sanitize_text_field((string) $value);
                if ($value !== '' && !in_array($value, $row_competitors, true)) {
                    $row_competitors[] = $value;
                }
            }
            if (empty($row_competitors)) {
                $row_competitors = $competitor_domains;
            }

            $lead_domain = sanitize_text_field((string) ($row_competitors[0] ?? ''));
            $signal_key = $norm . '|' . strtolower($lead_domain);
            if ($signal_key === '|' || isset($seen[$signal_key])) {
                continue;
            }
            $seen[$signal_key] = true;

            $signals[] = array(
                'competitor_title' => sanitize_text_field((string) ($row['competitor_title'] ?? $row['title'] ?? '')),
                'competitor_domain' => $lead_domain,
                'competitor_domains' => array_slice($row_competitors, 0, 5),
                'source_keyword' => $keyword,
                'gap_type' => sanitize_key((string) ($row['gap_type'] ?? 'missing')),
                'search_volume' => intval($row['search_volume'] ?? 0),
                'competitor_count' => intval($row['competitor_count'] ?? count($row_competitors)),
                'cluster_id' => (string) ($cluster['cluster_id'] ?? ''),
                'cluster_name' => (string) ($cluster['cluster_name'] ?? $keyword),
                'primary_keyword' => (string) ($cluster['primary_keyword'] ?? $keyword),
                'target_keywords' => array_values((array) ($cluster['target_keywords'] ?? array($keyword))),
                'intent' => (string) ($cluster['intent'] ?? 'informational'),
                'mapping_status' => (string) ($cluster['mapping_status'] ?? 'not_mapped'),
            );

            if (count($signals) >= 18) {
                break;
            }
        }

        if (empty($signals)) {
            foreach ($cluster_groups as $group) {
                if (!is_array($group)) {
                    continue;
                }
                $primary_keyword = sanitize_text_field((string) ($group['primary_keyword'] ?? $group['cluster_name'] ?? ''));
                $target_keywords = array_values(array_filter(array_map('sanitize_text_field', (array) ($group['target_keywords'] ?? $group['cluster_keywords'] ?? array()))));
                if ($primary_keyword === '' && !empty($target_keywords)) {
                    $primary_keyword = (string) $target_keywords[0];
                }
                if ($primary_keyword === '') {
                    continue;
                }
                $signals[] = array(
                    'competitor_title' => '',
                    'competitor_domain' => sanitize_text_field((string) ($competitor_domains[0] ?? '')),
                    'competitor_domains' => array_slice($competitor_domains, 0, 5),
                    'source_keyword' => $primary_keyword,
                    'gap_type' => 'theme_gap',
                    'search_volume' => 0,
                    'competitor_count' => count($competitor_domains),
                    'cluster_id' => sanitize_text_field((string) ($group['cluster_id'] ?? '')),
                    'cluster_name' => sanitize_text_field((string) ($group['cluster_name'] ?? $primary_keyword)),
                    'primary_keyword' => $primary_keyword,
                    'target_keywords' => array_slice($target_keywords, 0, 6),
                    'intent' => sanitize_key((string) ($group['intent'] ?? 'informational')),
                    'mapping_status' => sanitize_key((string) ($group['mapping_status'] ?? 'not_mapped')),
                );
                if (count($signals) >= 12) {
                    break;
                }
            }
        }

        return $signals;
    }

    private function aysa_adjustments_enrich_competitor_generated_items($items, $payload) {
        $items = is_array($items) ? $items : array();
        $payload = is_array($payload) ? $payload : array();

        $fresh_rows = isset($payload['competitor_fresh_content']['rows']) && is_array($payload['competitor_fresh_content']['rows'])
            ? $payload['competitor_fresh_content']['rows']
            : array();
        $signal_rows = isset($payload['competitor_title_signals']) && is_array($payload['competitor_title_signals'])
            ? $payload['competitor_title_signals']
            : array();

        $fresh_by_title = array();
        foreach ($fresh_rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $title = sanitize_text_field((string) ($row['title'] ?? ''));
            if ($title === '') {
                continue;
            }
            $fresh_by_title[strtolower($title)] = array(
                'competitor_title' => $title,
                'competitor_domain' => sanitize_text_field((string) ($row['competitor_domain'] ?? '')),
                'competitor_url' => esc_url_raw((string) ($row['url'] ?? '')),
            );
        }

        $signal_by_title = array();
        foreach ($signal_rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $title = sanitize_text_field((string) ($row['competitor_title'] ?? ''));
            if ($title === '') {
                continue;
            }
            $signal_by_title[strtolower($title)] = array(
                'competitor_title' => $title,
                'competitor_domain' => sanitize_text_field((string) ($row['competitor_domain'] ?? '')),
                'competitor_url' => '',
            );
        }

        foreach ($items as &$item) {
            if (!is_array($item)) {
                continue;
            }
            $title_key = strtolower(sanitize_text_field((string) ($item['competitor_title'] ?? '')));
            $match = array();
            if ($title_key !== '' && isset($fresh_by_title[$title_key])) {
                $match = $fresh_by_title[$title_key];
            } elseif ($title_key !== '' && isset($signal_by_title[$title_key])) {
                $match = $signal_by_title[$title_key];
            }

            if (!empty($match)) {
                if (empty($item['competitor_title'])) {
                    $item['competitor_title'] = (string) ($match['competitor_title'] ?? '');
                }
                if (empty($item['competitor_domain'])) {
                    $item['competitor_domain'] = (string) ($match['competitor_domain'] ?? '');
                }
                if (empty($item['competitor_url'])) {
                    $item['competitor_url'] = (string) ($match['competitor_url'] ?? '');
                }
            }
        }
        unset($item);

        return $items;
    }

    private function aysa_adjustments_build_competitor_analysis_context($cluster_groups, $payload, $title_signals, $fresh_discovery = array()) {
        $cluster_groups = is_array($cluster_groups) ? $cluster_groups : array();
        $payload = is_array($payload) ? $payload : array();
        $title_signals = is_array($title_signals) ? $title_signals : array();
        $fresh_discovery = is_array($fresh_discovery) ? $fresh_discovery : array();

        $domains = array();
        foreach ($title_signals as $signal) {
            if (!is_array($signal)) {
                continue;
            }
            $domain = sanitize_text_field((string) ($signal['competitor_domain'] ?? ''));
            if ($domain !== '' && !in_array($domain, $domains, true)) {
                $domains[] = $domain;
            }
        }
        foreach ((array) ($fresh_discovery['rows'] ?? array()) as $row) {
            if (!is_array($row)) {
                continue;
            }
            $domain = sanitize_text_field((string) ($row['competitor_domain'] ?? ''));
            if ($domain !== '' && !in_array($domain, $domains, true)) {
                $domains[] = $domain;
            }
        }

        return array(
            'business_type' => trim((string) ($payload['industry'] ?? '')),
            'business_name' => trim((string) ($payload['business_name'] ?? '')),
            'city' => trim((string) ($payload['city'] ?? '')),
            'target_audience' => trim((string) ($payload['company_info'] ?? '')),
            'competitor_domains' => array_slice($domains, 0, 5),
            'clusters_in_scope' => count($cluster_groups),
            'signal_count' => count($title_signals),
            'fresh_article_count' => intval($fresh_discovery['count'] ?? 0),
            'fresh_provider' => trim((string) ($fresh_discovery['provider'] ?? '')),
            'fresh_date_from' => trim((string) ($fresh_discovery['date_from'] ?? '')),
            'fresh_date_to' => trim((string) ($fresh_discovery['date_to'] ?? '')),
            'focus' => 'Respond to competitor themes with clearer, more useful, more strategic titles that fit the business and selected keyword clusters.',
            'fresh_discovery_status' => !empty($fresh_discovery['ok'])
                ? ((intval($fresh_discovery['count'] ?? 0) > 0) ? 'connected_with_rows' : 'connected_empty')
                : 'connected_error',
        );
    }

    private function aysa_adjustments_get_competitor_fresh_content($project_id, $competitor_domains) {
        $project_id = intval($project_id);
        $competitor_domains = array_values(array_filter(array_map('sanitize_text_field', (array) $competitor_domains)));
        if ($project_id <= 0 || empty($competitor_domains)) {
            return array(
                'ok' => false,
                'rows' => array(),
                'count' => 0,
                'provider' => '',
                'date_from' => '',
                'date_to' => '',
                'errors' => array(array('error' => 'missing_project_or_competitors')),
            );
        }

        $cache_key = 'competitor_fresh_content_v1';
        $memory = $this->aysa_adjustments_get_competitor_fresh_memory($project_id);
        $cached = $this->aysa_keyword_v2_cache_get($project_id, $cache_key);
        if (is_array($cached)) {
            $cached_domains = array_values(array_filter(array_map('sanitize_text_field', (array) ($cached['competitors'] ?? array()))));
            $cached_at = strtotime((string) ($cached['cache_meta']['cached_at'] ?? ''));
            $cached_count = intval($cached['count'] ?? (is_array($cached['rows'] ?? null) ? count((array) $cached['rows']) : 0));
            if ($cached_domains === $competitor_domains
                && $cached_at !== false
                && $cached_at >= (time() - 12 * HOUR_IN_SECONDS)
                && $cached_count > 0) {
                return $cached;
            }
        }

        $date_to = gmdate('Y-m-d');
        $date_from = gmdate('Y-m-d', strtotime('-30 days'));
        $payload = array(
            'competitors' => $competitor_domains,
            'date_from' => $date_from,
            'date_to' => $date_to,
            'limit_per_domain' => 5,
            'language' => 'ro',
        );

        $result = $this->aysa_broker_request('POST', '/v1/competitors/fresh-content/google', $payload, 90);
        if (is_wp_error($result)) {
            return array(
                'ok' => false,
                'rows' => array(),
                'count' => 0,
                'competitors' => $competitor_domains,
                'provider' => '',
                'date_from' => $date_from,
                'date_to' => $date_to,
                'errors' => array(array('error' => $result->get_error_message())),
            );
        }

        if (is_array($result) && isset($result['data']) && is_array($result['data'])) {
            $result = $result['data'];
        }

        if (!is_array($result)) {
            return array(
                'ok' => false,
                'rows' => array(),
                'count' => 0,
                'competitors' => $competitor_domains,
                'provider' => '',
                'date_from' => $date_from,
                'date_to' => $date_to,
                'errors' => array(array('error' => 'invalid_fresh_content_response')),
            );
        }

        $memory_seen_keys = array_fill_keys(array_values(array_filter(array_map('strval', (array) ($memory['seen_keys'] ?? array())))), true);
        foreach ($this->aysa_adjustments_get_existing_competitor_content_keys($project_id) as $existing_seen_key) {
            $memory_seen_keys[$existing_seen_key] = true;
        }

        $rows = array();
        $persist_seen_keys = array_keys($memory_seen_keys);
        foreach ((array) ($result['rows'] ?? array()) as $row) {
            if (!is_array($row)) {
                continue;
            }
            $title = sanitize_text_field((string) ($row['title'] ?? ''));
            $url = esc_url_raw((string) ($row['url'] ?? ''));
            if ($title === '' || $url === '') {
                continue;
            }
            if (preg_match('/^\-\s*[A-Z0-9.-]+$/i', $title)) {
                continue;
            }
            $memory_key = $this->aysa_adjustments_build_competitor_content_key($row);
            if ($memory_key !== '' && isset($memory_seen_keys[$memory_key])) {
                continue;
            }
            $rows[] = array(
                'competitor_domain' => sanitize_text_field((string) ($row['competitor_domain'] ?? '')),
                'title' => $title,
                'url' => $url,
                'snippet' => sanitize_text_field((string) ($row['snippet'] ?? '')),
                'date_hint' => sanitize_text_field((string) ($row['date_hint'] ?? '')),
                'query' => sanitize_text_field((string) ($row['query'] ?? '')),
                'source' => sanitize_key((string) ($row['source'] ?? '')),
            );
            if ($memory_key !== '') {
                $persist_seen_keys[] = $memory_key;
            }
            if (count($rows) >= 20) {
                break;
            }
        }

        $persist_seen_keys = array_values(array_unique(array_filter(array_map('strval', $persist_seen_keys))));
        if (count($persist_seen_keys) > 500) {
            $persist_seen_keys = array_slice($persist_seen_keys, -500);
        }

        $normalized = array(
            'ok' => !empty($result['ok']),
            'rows' => $rows,
            'count' => count($rows),
            'competitors' => $competitor_domains,
            'provider' => sanitize_text_field((string) ($result['provider'] ?? 'google_simple_crawler')),
            'date_from' => sanitize_text_field((string) ($result['date_from'] ?? $date_from)),
            'date_to' => sanitize_text_field((string) ($result['date_to'] ?? $date_to)),
            'errors' => isset($result['errors']) && is_array($result['errors']) ? $result['errors'] : array(),
            'last_checked_at' => current_time('mysql'),
            'last_checked_date_to' => $date_to,
            'memory_seen_count' => count($persist_seen_keys),
            'cache_meta' => array(
                'source' => 'live_refresh',
                'cached_at' => gmdate('c'),
            ),
        );

        $this->aysa_keyword_v2_cache_set($project_id, $cache_key, $normalized);
        $this->aysa_adjustments_set_competitor_fresh_memory($project_id, array(
            'last_checked_at' => current_time('mysql'),
            'last_checked_date_to' => $date_to,
            'seen_keys' => $persist_seen_keys,
        ));

        return $normalized;
    }

    private function aysa_adjustments_get_competitor_fresh_memory($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array(
                'last_checked_at' => '',
                'last_checked_date_to' => '',
                'seen_keys' => array(),
            );
        }

        $memory = get_option('aysa_competitor_fresh_memory_' . $project_id, array());
        if (!is_array($memory)) {
            $memory = array();
        }

        return array(
            'last_checked_at' => sanitize_text_field((string) ($memory['last_checked_at'] ?? '')),
            'last_checked_date_to' => sanitize_text_field((string) ($memory['last_checked_date_to'] ?? '')),
            'seen_keys' => array_values(array_filter(array_map('strval', (array) ($memory['seen_keys'] ?? array())))),
        );
    }

    private function aysa_adjustments_set_competitor_fresh_memory($project_id, $memory) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return;
        }

        $memory = is_array($memory) ? $memory : array();
        update_option('aysa_competitor_fresh_memory_' . $project_id, array(
            'last_checked_at' => sanitize_text_field((string) ($memory['last_checked_at'] ?? '')),
            'last_checked_date_to' => sanitize_text_field((string) ($memory['last_checked_date_to'] ?? '')),
            'seen_keys' => array_values(array_filter(array_map('strval', (array) ($memory['seen_keys'] ?? array())))),
        ), false);
    }

    private function aysa_adjustments_build_competitor_content_key($row) {
        $row = is_array($row) ? $row : array();
        $url = esc_url_raw((string) ($row['url'] ?? ''));
        if ($url !== '') {
            return 'url:' . strtolower($url);
        }

        $domain = sanitize_text_field((string) ($row['competitor_domain'] ?? ''));
        $title = sanitize_text_field((string) ($row['title'] ?? $row['competitor_title'] ?? ''));
        if ($domain === '' || $title === '') {
            return '';
        }

        return 'title:' . strtolower($domain . '|' . $title);
    }

    private function aysa_adjustments_get_existing_competitor_content_keys($project_id) {
        global $wpdb;

        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array();
        }

        $table = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $rows = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT strategy_meta FROM {$table}
             WHERE project_id = %d
               AND strategy_type = %s
               AND COALESCE(automation_status, '') <> 'deleted'",
            $project_id,
            'competitor_derived_titles'
        ), ARRAY_A);

        $keys = array();
        foreach ((array) $rows as $row) {
            $meta = isset($row['strategy_meta']) ? json_decode((string) $row['strategy_meta'], true) : array();
            if (!is_array($meta) || empty($meta)) {
                continue;
            }

            $key = $this->aysa_adjustments_build_competitor_content_key(array(
                'url' => $meta['competitor_url'] ?? '',
                'competitor_domain' => $meta['competitor_domain'] ?? '',
                'title' => $meta['competitor_title'] ?? '',
            ));
            if ($key !== '') {
                $keys[] = $key;
            }
        }

        return array_values(array_unique($keys));
    }

    private function aysa_keyword_mapping_v1_build_payload($project_id) {
        $project_id = intval($project_id);
        $setup_user_id = intval($this->aysa_get_authenticated_external_user_id());
        if ($setup_user_id <= 0) {
            $setup_user_id = get_current_user_id();
        }

        return $this->aysa_collect_research_context_bundle($project_id, $setup_user_id);
    }

    private function aysa_collect_research_context_bundle($project_id, $setup_user_id = 0) {
        $project_id = intval($project_id);
        $setup_user_id = intval($setup_user_id);
        if ($setup_user_id <= 0) {
            $setup_user_id = intval($this->aysa_get_authenticated_external_user_id());
        }
        if ($setup_user_id <= 0) {
            $setup_user_id = get_current_user_id();
        }

        $shared_user_id = intval(get_option('aysa_user_id', 0));
        $profile_user_id = $setup_user_id;

        $general = get_option('aysa_wizard_general_' . intval($profile_user_id), array());
        $objectives = get_option('aysa_wizard_objectives_' . intval($profile_user_id), array());
        $seo_settings = get_option('aysa_wizard_seo-settings_' . intval($profile_user_id), array());
        $seed_data = $this->aysa_keyword_strategy_extract_seed_data($profile_user_id);

        $has_seed_data = !empty($seed_data['seed_keywords']) || !empty($seed_data['competitors']);
        if ((!$has_seed_data || $profile_user_id <= 0) && $shared_user_id > 0 && $shared_user_id !== $profile_user_id) {
            $shared_general = get_option('aysa_wizard_general_' . intval($shared_user_id), array());
            $shared_objectives = get_option('aysa_wizard_objectives_' . intval($shared_user_id), array());
            $shared_seo_settings = get_option('aysa_wizard_seo-settings_' . intval($shared_user_id), array());
            $shared_seed_data = $this->aysa_keyword_strategy_extract_seed_data($shared_user_id);
            $shared_has_seed_data = !empty($shared_seed_data['seed_keywords']) || !empty($shared_seed_data['competitors']);
            if ($shared_has_seed_data) {
                $profile_user_id = $shared_user_id;
                $general = $shared_general;
                $objectives = $shared_objectives;
                $seo_settings = $shared_seo_settings;
                $seed_data = $shared_seed_data;
            }
        }

        $site_host = wp_parse_url(home_url(), PHP_URL_HOST);
        $site_host = is_string($site_host) ? trim($site_host) : '';

        return array(
            'project_id' => $project_id,
            'site_host' => $site_host,
            'profile_user_id' => $profile_user_id,
            'general' => is_array($general) ? $general : array(),
            'objectives' => is_array($objectives) ? $objectives : array(),
            'seo_settings' => is_array($seo_settings) ? $seo_settings : array(),
            'seed_data' => is_array($seed_data) ? $seed_data : array(),
            'research_snapshot' => $this->aysa_adjustments_get_keyword_research_snapshot($project_id, $profile_user_id),
            'monitoring_context' => $this->aysa_monitoring_resolve_context(''),
            'site_context' => array(
                'page_inventory' => $this->aysa_keyword_mapping_collect_site_page_inventory(),
            ),
        );
    }

    private function aysa_collect_content_strategy_context_bundle($project_id, $setup_user_id = 0) {
        $bundle = $this->aysa_collect_research_context_bundle($project_id, $setup_user_id);
        $bundle['mapping_workspace'] = $this->aysa_keyword_v2_cache_get(intval($project_id), 'keyword_mapping_v2');
        $bundle['missing_pages_workspace'] = $this->aysa_keyword_v2_cache_get(intval($project_id), 'missing_pages_v2');
        $bundle['content_strategy_workspace'] = $this->aysa_keyword_v2_cache_get(intval($project_id), 'content_strategy_v2');
        $bundle['research_snapshot'] = $this->aysa_adjustments_get_research_snapshot(intval($project_id), intval($setup_user_id));
        return $bundle;
    }

    private function aysa_keyword_mapping_collect_site_page_inventory() {
        $items = array();
        $seen = array();

        $push = static function(array &$itemsRef, array &$seenRef, $url, $title, $type, $taxonomy = '', array $meta = array()) {
            $url = esc_url_raw((string) $url);
            $title = sanitize_text_field((string) $title);
            $type = sanitize_key((string) $type);
            $taxonomy = sanitize_key((string) $taxonomy);
            if ($url === '' || $title === '' || $type === '') {
                return;
            }
            $key = md5($url . '|' . $type . '|' . $taxonomy);
            if (isset($seenRef[$key])) {
                return;
            }
            $seenRef[$key] = true;
            $itemsRef[] = array(
                'url' => $url,
                'title' => $title,
                'type' => $type,
                'taxonomy' => $taxonomy,
                'slug' => sanitize_title((string) ($meta['slug'] ?? '')),
                'path' => sanitize_text_field((string) ($meta['path'] ?? '')),
                'semantic_terms' => array_values(array_filter(array_map('sanitize_text_field', (array) ($meta['semantic_terms'] ?? array())))),
                'excerpt' => sanitize_text_field((string) ($meta['excerpt'] ?? '')),
            );
        };

        $push($items, $seen, home_url('/'), get_bloginfo('name'), 'home');

        $posts = get_posts(array(
            'numberposts' => 60,
            'post_status' => 'publish',
            'post_type' => 'post',
            'orderby' => 'date',
            'order' => 'DESC',
        ));
        foreach ((array) $posts as $post) {
            $push($items, $seen, get_permalink($post->ID), $post->post_title, 'post', '', array(
                'slug' => $post->post_name,
                'path' => wp_parse_url(get_permalink($post->ID), PHP_URL_PATH),
                'excerpt' => wp_trim_words(wp_strip_all_tags((string) ($post->post_excerpt ?: $post->post_content)), 24, ''),
            ));
        }

        $pages = get_pages(array(
            'number' => 60,
            'post_status' => 'publish',
            'sort_column' => 'post_date',
            'sort_order' => 'DESC',
        ));
        foreach ((array) $pages as $page) {
            $push($items, $seen, get_permalink($page->ID), $page->post_title, 'page', '', array(
                'slug' => $page->post_name,
                'path' => wp_parse_url(get_permalink($page->ID), PHP_URL_PATH),
                'excerpt' => wp_trim_words(wp_strip_all_tags((string) ($page->post_excerpt ?: $page->post_content)), 24, ''),
            ));
        }

        $products = get_posts(array(
            'numberposts' => 120,
            'post_status' => 'publish',
            'post_type' => 'product',
            'orderby' => 'date',
            'order' => 'DESC',
        ));
        foreach ((array) $products as $product) {
            $product_terms = wp_get_post_terms($product->ID, 'product_cat', array('fields' => 'names'));
            $push($items, $seen, get_permalink($product->ID), $product->post_title, 'product', '', array(
                'slug' => $product->post_name,
                'path' => wp_parse_url(get_permalink($product->ID), PHP_URL_PATH),
                'semantic_terms' => is_array($product_terms) ? $product_terms : array(),
                'excerpt' => wp_trim_words(wp_strip_all_tags((string) ($product->post_excerpt ?: $product->post_content)), 24, ''),
            ));
        }

        $taxonomies = array('category', 'product_cat', 'post_tag', 'product_tag');
        foreach ($taxonomies as $taxonomy) {
            $terms = get_terms(array(
                'taxonomy' => $taxonomy,
                'hide_empty' => false,
                'number' => 150,
            ));
            if (is_wp_error($terms) || !is_array($terms)) {
                continue;
            }
            foreach ($terms as $term) {
                if (!$term || is_wp_error($term)) {
                    continue;
                }
                $term_link = get_term_link($term);
                if (is_wp_error($term_link)) {
                    continue;
                }
                $type = in_array($taxonomy, array('category', 'product_cat'), true) ? 'category' : 'tag';
                $semantic_terms = array($term->name);
                if (is_taxonomy_hierarchical($taxonomy)) {
                    $ancestor_ids = array_reverse((array) get_ancestors((int) $term->term_id, $taxonomy, 'taxonomy'));
                    foreach ($ancestor_ids as $ancestor_id) {
                        $ancestor = get_term((int) $ancestor_id, $taxonomy);
                        if ($ancestor && !is_wp_error($ancestor) && !empty($ancestor->name)) {
                            $semantic_terms[] = (string) $ancestor->name;
                        }
                    }
                }
                $push($items, $seen, $term_link, $term->name, $type, $taxonomy, array(
                    'slug' => $term->slug,
                    'path' => wp_parse_url($term_link, PHP_URL_PATH),
                    'semantic_terms' => $semantic_terms,
                    'excerpt' => wp_trim_words(wp_strip_all_tags((string) ($term->description ?? '')), 20, ''),
                ));
            }
        }

        return array_values($items);
    }

    private function aysa_agent8_get_monitoring_rank_tracking_snapshot($project_id) {
        $snapshot = $this->aysa_adjustments_get_monitoring_ranking_snapshot($project_id);
        return is_array($snapshot) ? $snapshot : array();
    }

    private function aysa_agent8_get_monitoring_ai_overview_snapshot($project_id) {
        $context = $this->aysa_monitoring_resolve_context('');
        $target_host = (string) ($context['target'] ?? '');
        if ($target_host === '') {
            return array();
        }

        $overview = $this->aysa_monitoring_ai_get_local_overview($project_id, $target_host);
        return is_array($overview) ? $overview : array();
    }

    private function aysa_agent8_get_monitoring_query_packs_snapshot($project_id) {
        $overview = $this->aysa_agent8_get_monitoring_ai_overview_snapshot($project_id);
        $history = isset($overview['history']) && is_array($overview['history']) ? array_values($overview['history']) : array();
        $snapshot = isset($overview['snapshot']) && is_array($overview['snapshot']) ? $overview['snapshot'] : array();
        $latest_query_pack = isset($snapshot['query_pack']) && is_array($snapshot['query_pack'])
            ? array_values($snapshot['query_pack'])
            : array();
        if ($latest_query_pack === array() && isset($history[0]['query_pack']) && is_array($history[0]['query_pack'])) {
            $latest_query_pack = array_values($history[0]['query_pack']);
        }

        return array(
            'domain' => (string) ($overview['domain'] ?? ''),
            'latest_query_pack' => $latest_query_pack,
            'history' => $history,
            'snapshot_at' => (string) ($snapshot['snapshot_at'] ?? ($history[0]['snapshot_at'] ?? '')),
        );
    }

    private function aysa_agent8_get_research_sitemap_competition_snapshot($project_id) {
        $project_id = intval($project_id);
        $target_host = $this->aysa_monitoring_extract_host((string) home_url('/'));
        if ($project_id <= 0 || $target_host === '') {
            return array();
        }
        $overview = $this->aysa_sitemap_competition_get_local_overview($project_id, $target_host);
        return is_array($overview) ? $overview : array();
    }

    private function aysa_agent8_apply_research_keyword_discovery_refresh($project_id) {
        $job = $this->aysa_project_jobs_enqueue(
            intval($project_id),
            'research',
            'keyword_discovery_refresh',
            array(
                'apply_to_wp' => true,
                'refresh_kind' => 'gsc_auto',
            ),
            array(
                'dedupe_key' => md5(wp_json_encode(array(
                    'job_type' => 'keyword_discovery_refresh',
                    'refresh_kind' => 'gsc_auto',
                    'apply_to_wp' => 1,
                ))),
                'input_summary' => array(
                    'refresh_kind' => 'gsc_auto',
                    'apply_to_wp' => 1,
                ),
            )
        );
        if (is_wp_error($job)) {
            return array('applied' => false, 'reason' => sanitize_key((string) $job->get_error_code()), 'message' => $job->get_error_message());
        }
        $job_row = isset($job['job']) && is_array($job['job']) ? $job['job'] : array();
        return array(
            'applied' => true,
            'operation' => 'refresh_research_keyword_discovery',
            'summary' => array('status' => (string) ($job_row['status'] ?? 'queued')),
            'snapshot' => $this->aysa_adjustments_get_research_snapshot($project_id),
            'async_job' => $this->aysa_project_jobs_to_rack($this->aysa_project_jobs_get_state($project_id)),
        );
    }

    private function aysa_agent8_apply_research_competitor_gap_refresh($project_id) {
        $project_id = intval($project_id);
        $context = $this->aysa_collect_research_context_bundle($project_id);
        $target = (string) wp_parse_url(home_url('/'), PHP_URL_HOST);
        $resolved = $this->aysa_monitoring_resolve_context($target);
        $resolved_target = trim((string) ($resolved['target'] ?? $target));
        $competitors = array_values(array_unique(array_filter(array_map(function($domain) {
            return $this->aysa_monitoring_extract_host((string) $domain);
        }, (array) ($context['seed_data']['competitors'] ?? array())), function($domain) {
            return $domain !== '';
        })));
        if ($project_id <= 0 || $resolved_target === '' || $competitors === array()) {
            return array('applied' => false, 'reason' => 'competitor_gap_context_required');
        }
        $payload = array(
            'project_id' => $project_id,
            'target' => $resolved_target,
            'location_code' => intval($resolved['location_code'] ?? 2840),
            'language_code' => (string) ($resolved['language_code'] ?? 'ro'),
            'limit' => 200,
            'competitors' => $competitors,
        );
        $request = $this->aysa_research_async_prepare_competitor_gap_request(array(
            'project_id' => $project_id,
            'target' => $resolved_target,
            'limit' => 200,
            'competitors' => $competitors,
        ));
        if (is_wp_error($request)) {
            return array('applied' => false, 'reason' => sanitize_key((string) $request->get_error_code()), 'message' => $request->get_error_message());
        }
        $job = $this->aysa_project_jobs_enqueue(
            $project_id,
            'research',
            'competitor_gap',
            array(
                'task' => 'competitor_gap',
                'request' => $request,
            ),
            array(
                'dedupe_key' => (string) ($request['request_hash'] ?? ''),
                'input_summary' => array(
                    'target' => $resolved_target,
                    'competitors_count' => count($competitors),
                ),
            )
        );
        if (is_wp_error($job)) {
            return array('applied' => false, 'reason' => sanitize_key((string) $job->get_error_code()), 'message' => $job->get_error_message());
        }
        $job_row = isset($job['job']) && is_array($job['job']) ? $job['job'] : array();
        return array(
            'applied' => true,
            'operation' => 'refresh_research_competitor_gap',
            'summary' => array('status' => (string) ($job_row['status'] ?? 'queued'), 'competitors' => count($competitors)),
            'snapshot' => $this->aysa_adjustments_get_research_snapshot($project_id),
            'async_job' => isset($job['rack']) && is_array($job['rack']) ? $job['rack'] : array(),
        );
    }

    private function aysa_agent8_apply_research_competitor_identification_refresh($project_id) {
        $project_id = intval($project_id);
        $context = $this->aysa_collect_research_context_bundle($project_id);
        $seed_keywords = array_values(array_filter(array_map('trim', (array) ($context['seed_data']['seed_keywords'] ?? array()))));
        if ($project_id <= 0 || $seed_keywords === array()) {
            return array('applied' => false, 'reason' => 'seed_keywords_required');
        }
        $resolved = $this->aysa_monitoring_resolve_context((string) wp_parse_url(home_url('/'), PHP_URL_HOST));
        $request_payload = array(
            'project_id' => $project_id,
            'seed_keywords' => $seed_keywords,
            'active_competitors' => array_values((array) ($context['seed_data']['competitors'] ?? array())),
            'location_code' => intval($resolved['location_code'] ?? 2840),
            'language_code' => strtolower(trim((string) ($resolved['language_code'] ?? 'ro'))),
            'top_n' => 10,
            'own_domains' => array_values(array_filter(array(strtolower((string) wp_parse_url(home_url('/'), PHP_URL_HOST))))),
        );
        $request = $this->aysa_research_async_prepare_competitor_identification_request(array(
            'project_id' => $project_id,
            'top_n' => 10,
        ));
        if (is_wp_error($request)) {
            return array('applied' => false, 'reason' => sanitize_key((string) $request->get_error_code()), 'message' => $request->get_error_message());
        }
        $job = $this->aysa_project_jobs_enqueue(
            $project_id,
            'research',
            'competitor_identification',
            array(
                'task' => 'competitor_identification',
                'request' => $request,
            ),
            array(
                'dedupe_key' => (string) ($request['request_hash'] ?? ''),
                'input_summary' => array(
                    'seed_keywords_count' => count($seed_keywords),
                ),
            )
        );
        if (is_wp_error($job)) {
            return array('applied' => false, 'reason' => sanitize_key((string) $job->get_error_code()), 'message' => $job->get_error_message());
        }
        $job_row = isset($job['job']) && is_array($job['job']) ? $job['job'] : array();
        return array(
            'applied' => true,
            'operation' => 'refresh_research_competitor_identification',
            'summary' => array('status' => (string) ($job_row['status'] ?? 'queued')),
            'snapshot' => $this->aysa_adjustments_get_research_snapshot($project_id),
            'async_job' => isset($job['rack']) && is_array($job['rack']) ? $job['rack'] : array(),
        );
    }

    private function aysa_agent8_apply_research_competitor_backlinks_refresh($project_id) {
        $project_id = intval($project_id);
        $home_host = $this->aysa_monitoring_extract_host((string) home_url('/'));
        $resolved_context = $this->aysa_monitoring_resolve_context($home_host);
        $target = strtolower(trim((string) ($resolved_context['target'] ?? '')));
        if ($target !== '') {
            $target = $this->aysa_monitoring_extract_host($target);
        }
        if ($target === '') {
            $target = $home_host;
        }
        if ($project_id <= 0 || $target === '') {
            return array('applied' => false, 'reason' => 'target_domain_required');
        }

        $context = $this->aysa_collect_research_context_bundle($project_id);
        $competitors = array_values((array) ($context['seed_data']['competitors'] ?? array()));
        $normalized_competitors = array();
        foreach ($competitors as $raw) {
            $host = $this->aysa_monitoring_extract_host((string) $raw);
            if ($host === '') {
                $host = strtolower(trim((string) $raw));
            }
            $host = preg_replace('/^www\./', '', $host);
            if ($host === '' || $host === $target || !preg_match('/^[a-z0-9.-]+\.[a-z]{2,}$/', $host)) {
                continue;
            }
            $normalized_competitors[$host] = true;
        }
        $competitor_domains = array_keys($normalized_competitors);
        if ($competitor_domains === array()) {
            return array('applied' => false, 'reason' => 'no_competitors_configured');
        }

        $domains = array_merge(array($target), $competitor_domains);
        foreach ($domains as $domain) {
            $broker_result = $this->aysa_broker_request('POST', '/v1/dataforseo/backlink-audit', array(
                'target' => $domain,
                'domain' => $domain,
                'limit' => 150,
                'project_id' => $project_id,
            ), 180);
            if (is_wp_error($broker_result)) {
                continue;
            }
            $items = (isset($broker_result['items']) && is_array($broker_result['items'])) ? $broker_result['items'] : array();
            $snapshot = array(
                'domain' => $domain,
                'provider' => (string) ($broker_result['provider'] ?? 'dataforseo'),
                'generated_at' => current_time('mysql'),
                'count' => count($items),
                'items' => $items,
            );
            update_option($this->aysa_get_backlink_audit_domain_key($project_id, $domain), $snapshot, false);
        }
        $this->aysa_wallet_track_action_usage($project_id, 'aysa_competitor_backlinks_gap_insights', 'competitor_backlinks_gap_insights', array('module' => 'competitor_backlinks', 'source' => 'agent8_refresh', 'target' => $target, 'competitors_count' => count($competitor_domains), 'force_refresh' => 1), 45, '/v1/backlinks/gap-insights');
        return array(
            'applied' => true,
            'operation' => 'refresh_research_competitor_backlinks',
            'summary' => array('status' => 'refreshed'),
            'snapshot' => $this->aysa_adjustments_get_research_snapshot($project_id),
        );
    }

    private function aysa_agent8_apply_research_sitemap_competition_refresh($project_id) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array('applied' => false, 'reason' => 'invalid_project_id');
        }
        $payload = $this->aysa_sitemap_competition_build_payload();
        $job = $this->aysa_project_jobs_enqueue(
            intval($payload['project_id'] ?? 0),
            'research',
            'sitemap_competition_scan',
            array(
                'project_id' => intval($payload['project_id'] ?? 0),
                'target_host' => (string) ($payload['target_host'] ?? ''),
                'competitors' => isset($payload['competitors']) && is_array($payload['competitors']) ? array_values($payload['competitors']) : array(),
            ),
            array(
                'dedupe_key' => md5(wp_json_encode(array(
                    'project_id' => intval($payload['project_id'] ?? 0),
                    'target_host' => (string) ($payload['target_host'] ?? ''),
                    'competitors' => isset($payload['competitors']) && is_array($payload['competitors']) ? array_values($payload['competitors']) : array(),
                ))),
                'input_summary' => array(
                    'target_host' => (string) ($payload['target_host'] ?? ''),
                    'competitors_count' => isset($payload['competitors']) && is_array($payload['competitors']) ? count($payload['competitors']) : 0,
                ),
            )
        );
        if (is_wp_error($job)) {
            return array('applied' => false, 'reason' => sanitize_key((string) $job->get_error_code()), 'message' => $job->get_error_message());
        }
        $job_row = isset($job['job']) && is_array($job['job']) ? $job['job'] : array();
        $local_run_id = 0;
        if (!empty($job_row['worker_ref'])) {
            $parts = explode(':', (string) $job_row['worker_ref']);
            $local_run_id = intval(end($parts));
        }
        $snapshot = $this->aysa_agent8_get_research_sitemap_competition_snapshot($project_id);
        return array(
            'applied' => true,
            'operation' => 'refresh_research_sitemap_competition',
            'summary' => array('status' => (string) ($job_row['status'] ?? 'queued'), 'local_run_id' => $local_run_id),
            'snapshot' => $snapshot,
            'async_job' => isset($job['rack']) && is_array($job['rack']) ? $job['rack'] : array(),
        );
    }

    private function aysa_agent8_get_monitoring_associations($project_id) {
        $associations = $this->get_keyword_discovery_local_associations(intval($project_id));
        $normalized = $this->aysa_monitoring_normalize_associations($associations);
        return is_array($normalized) ? array_values($normalized) : array();
    }

    private function aysa_agent8v3_sync_monitoring_worker_runs($project_id, $syncLocal = false) {
        $project_id = intval($project_id);
        if ($project_id <= 0) {
            return array('rank' => array(), 'ai' => array());
        }
        return array(
            'rank' => $this->aysa_agent8v3_sync_monitoring_worker_run($project_id, 'monitoring_rank_tracking_refresh', (bool) $syncLocal),
            'ai' => $this->aysa_agent8v3_sync_monitoring_worker_run($project_id, 'monitoring_ai_visibility_refresh', (bool) $syncLocal),
        );
    }

    private function aysa_agent8v3_sync_monitoring_worker_run($project_id, $run_type, $syncLocal = false) {
        $run_type = sanitize_key((string) $run_type);
        if (!in_array($run_type, array('monitoring_rank_tracking_refresh', 'monitoring_ai_visibility_refresh'), true)) {
            return array('ok' => false, 'error' => 'invalid_monitoring_run_type');
        }
        $response = $this->aysa_agent8v3_api_request('POST', '/v1/monitoring-runs/status', array(
            'project_id' => intval($project_id),
            'run_type' => $run_type,
            // The Monitoring workspace only needs durable status metadata. Keep
            // the full sync payload for the explicit local-sync branch below.
            'compact' => !$syncLocal,
        ));
        if (is_wp_error($response)) {
            return array(
                'ok' => false,
                'error' => $response->get_error_code(),
                'message' => $response->get_error_message(),
            );
        }
        if (empty($response['ok'])) {
            return is_array($response) ? $response : array('ok' => false, 'error' => 'monitoring_status_failed');
        }
        $run = isset($response['run']) && is_array($response['run']) ? $response['run'] : array();
        $run_id = intval($run['id'] ?? 0);
        $response['read_only_contract'] = $this->aysa_monitoring_read_only_contract('agent8_monitoring_status_no_local_sync');
        $response['local_sync'] = array('status' => 'not_synced_read_only');
        if ($syncLocal && $run_id > 0 && (string) ($run['status'] ?? '') === 'completed') {
            $sync_key = 'aysa_monitoring_v3_synced_run_' . intval($project_id) . '_' . $run_type . '_' . $run_id;
            if (!get_option($sync_key, false)) {
                $sync_payload = isset($response['sync_payload']) && is_array($response['sync_payload']) ? $response['sync_payload'] : array();
                if ($run_type === 'monitoring_rank_tracking_refresh') {
                    $rows = isset($sync_payload['rank_rows']) && is_array($sync_payload['rank_rows']) ? $sync_payload['rank_rows'] : array();
                    $target = sanitize_text_field((string) ($sync_payload['target'] ?? ''));
                    if (!empty($rows) && $target !== '') {
                        $response['local_sync'] = $this->aysa_monitoring_persist_rows($project_id, $target, $rows);
                        $this->aysa_monitoring_update_client_rag_context($project_id, $target);
                        update_option($sync_key, current_time('mysql'), false);
                    }
                } else {
                    $snapshot = isset($sync_payload['ai_snapshot']) && is_array($sync_payload['ai_snapshot']) ? $sync_payload['ai_snapshot'] : array();
                    $domain = sanitize_text_field((string) ($sync_payload['domain'] ?? ''));
                    if (!empty($snapshot) && $domain !== '') {
                        $response['local_sync'] = array('snapshot_id' => $this->aysa_monitoring_ai_persist_snapshot_summary($project_id, $domain, $snapshot));
                        $this->aysa_monitoring_update_client_rag_context($project_id, $domain);
                        update_option($sync_key, current_time('mysql'), false);
                    }
                }
            } else {
                $response['local_sync'] = array('status' => 'already_synced', 'synced_at' => get_option($sync_key));
            }
        }
        return $response;
    }

    private function aysa_agent8_apply_monitoring_rank_refresh($project_id) {
        $project_id = intval($project_id);
        $associations = $this->aysa_agent8_get_monitoring_associations($project_id);
        if ($project_id <= 0 || empty($associations)) {
            return array('applied' => false, 'reason' => 'no_tracked_keywords');
        }

        $existing_status = $this->aysa_monitoring_get_refresh_status($project_id);
        if (($existing_status['status'] ?? '') === 'running') {
            return array(
                'applied' => true,
                'operation' => 'refresh_monitoring_rank_tracking',
                'summary' => array(
                    'status' => (string) ($existing_status['status'] ?? 'running'),
                    'tracked_keywords' => count($associations),
                ),
                'snapshot' => $this->aysa_agent8_get_monitoring_rank_tracking_snapshot($project_id),
                'async_job' => $existing_status,
            );
        }

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_monitoring_rank_refresh_start_background',
            'monitoring_rank_refresh_start_background',
            array(
                'module' => 'monitoring_analytics',
                'source' => 'agent8_background_refresh',
            ),
            120,
            '/v1/dataforseo/rank-tracking/refresh-start-background'
        );

        $batch_size = 10;
        $context = $this->aysa_monitoring_resolve_context('');
        $run_id = uniqid('mon_', true);
        $now = time();

        $queue = array(
            'run_id' => $run_id,
            'project_id' => $project_id,
            'context' => $context,
            'associations' => $associations,
            'cursor' => 0,
            'batch_size' => $batch_size,
            'current_retry' => 0,
            'created_at' => $now,
        );
        update_option($this->aysa_monitoring_refresh_queue_key($project_id), $queue, false);

        $status = array(
            'run_id' => $run_id,
            'status' => 'running',
            'total' => count($associations),
            'processed' => 0,
            'refreshed' => 0,
            'failed' => 0,
            'deferred' => 0,
            'last_error' => '',
            'started_at' => $now,
            'updated_at' => $now,
            'finished_at' => 0,
        );
        $this->aysa_monitoring_save_refresh_status($project_id, $status);
        $this->aysa_monitoring_ensure_refresh_cron_tick($project_id);
        $this->aysa_monitoring_schedule_background_refresh($project_id, 1);

        return array(
            'applied' => true,
            'operation' => 'refresh_monitoring_rank_tracking',
            'summary' => array(
                'status' => 'running',
                'tracked_keywords' => count($associations),
            ),
            'snapshot' => $this->aysa_agent8_get_monitoring_rank_tracking_snapshot($project_id),
            'async_job' => $status,
        );
    }

    private function aysa_agent8_apply_monitoring_ai_refresh($project_id) {
        $project_id = intval($project_id);
        $payload = $this->aysa_monitoring_build_ai_payload();
        $domain = (string) ($payload['domain'] ?? '');
        if ($project_id <= 0 || $domain === '') {
            return array('applied' => false, 'reason' => 'monitoring_ai_context_required');
        }

        $status_key = $this->aysa_monitoring_ai_refresh_status_option_key($project_id, $domain);
        $result = $this->aysa_broker_request('POST', '/v1/monitoring/ai/refresh/start', $payload, 60);
        if (is_wp_error($result)) {
            return array(
                'applied' => false,
                'reason' => 'monitoring_ai_refresh_failed',
                'message' => $this->aysa_humanize_broker_error($result, 'AI monitoring refresh failed.'),
            );
        }

        $snapshot = isset($result['snapshot']) && is_array($result['snapshot']) ? $result['snapshot'] : array();
        if (!empty($snapshot)) {
            $this->aysa_monitoring_ai_persist_snapshot_summary($project_id, $domain, $snapshot);
        }

        $status = array(
            'ok' => true,
            'project_id' => $project_id,
            'domain' => $domain,
            'status' => (string) ($result['status'] ?? 'running'),
            'message' => (string) ($result['message'] ?? 'AI refresh started.'),
            'started_at' => gmdate('c'),
            'updated_at' => gmdate('c'),
            'snapshot_id' => intval($result['snapshot_id'] ?? 0),
            'error' => '',
            'processed_questions' => intval($result['processed_questions'] ?? 0),
            'total_questions' => intval($result['total_questions'] ?? 0),
            'processed_sources' => intval($result['processed_sources'] ?? 0),
            'total_sources' => intval($result['total_sources'] ?? 0),
            'partial_errors' => isset($result['partial_errors']) && is_array($result['partial_errors']) ? array_values($result['partial_errors']) : array(),
            'query_pack' => isset($result['query_pack']) && is_array($result['query_pack']) ? array_values($result['query_pack']) : array(),
            'overview_questions' => isset($result['overview_questions']) && is_array($result['overview_questions']) ? array_values($result['overview_questions']) : array(),
            'snapshot' => $snapshot,
            'history' => isset($result['history']) && is_array($result['history']) ? array_values($result['history']) : array(),
            'billed_provider_milli_cents' => 0,
        );
        $status = $this->aysa_monitoring_ai_finalize_status_payload($status, array());
        update_option($status_key, $status, false);
        $this->aysa_monitoring_schedule_ai_refresh_event($payload, 1);

        return array(
            'applied' => true,
            'operation' => 'refresh_monitoring_ai_monitoring',
            'summary' => array(
                'status' => (string) ($status['status'] ?? 'running'),
                'domain' => $domain,
            ),
            'snapshot' => $this->aysa_agent8_get_monitoring_ai_overview_snapshot($project_id),
            'async_job' => $status,
        );
    }

    private function aysa_agent8_apply_monitoring_keyword_overview_refresh($project_id) {
        $project_id = intval($project_id);
        $associations = $this->aysa_agent8_get_monitoring_associations($project_id);
        if ($project_id <= 0 || empty($associations)) {
            return array('applied' => false, 'reason' => 'no_tracked_keywords');
        }

        $request = $this->aysa_monitoring_keywords_prepare_request(array(
            'project_id' => $project_id,
            'associations' => wp_json_encode($associations),
        ));
        if (is_wp_error($request)) {
            return array(
                'applied' => false,
                'reason' => sanitize_key((string) $request->get_error_code()),
                'message' => $request->get_error_message(),
            );
        }

        $request_hash = (string) ($request['request_hash'] ?? '');
        $existing = $this->aysa_monitoring_keywords_async_get_status($project_id);
        if (
            is_array($existing) &&
            !empty($existing['run_id']) &&
            in_array((string) ($existing['status'] ?? ''), array('queued', 'running'), true) &&
            (string) ($existing['request_hash'] ?? '') === $request_hash
        ) {
            return array(
                'applied' => true,
                'operation' => 'refresh_monitoring_keyword_overview',
                'summary' => array(
                    'status' => (string) ($existing['status'] ?? 'running'),
                    'tracked_keywords' => count($associations),
                ),
                'snapshot' => $this->aysa_agent8_get_monitoring_rank_tracking_snapshot($project_id),
                'async_job' => $this->aysa_monitoring_keywords_async_output_status($existing),
            );
        }

        $this->aysa_wallet_track_action_usage(
            $project_id,
            'aysa_monitoring_keywords_overview',
            'monitoring_keywords_overview',
            array(
                'module' => 'monitoring_analytics',
                'source' => 'agent8_manual_refresh_async',
            ),
            60,
            '/v1/monitoring/keywords/overview'
        );

        $run_id = uniqid('mon_kw_', true);
        $status = array(
            'project_id' => $project_id,
            'domain' => (string) ($request['domain'] ?? ''),
            'run_id' => $run_id,
            'request_hash' => $request_hash,
            'status' => 'queued',
            'result' => null,
            'error' => '',
            'started_at' => time(),
            'updated_at' => time(),
            'finished_at' => 0,
            '_request' => $request,
        );
        $this->aysa_monitoring_keywords_async_save_status($project_id, $status);
        wp_schedule_single_event(time() + 1, 'aysa_monitoring_keywords_async_event', array(array(
            'project_id' => $project_id,
            'run_id' => $run_id,
            'request_hash' => $request_hash,
            'request' => $request,
        )));
        if (function_exists('spawn_cron')) {
            spawn_cron();
        }

        return array(
            'applied' => true,
            'operation' => 'refresh_monitoring_keyword_overview',
            'summary' => array(
                'status' => 'queued',
                'tracked_keywords' => count($associations),
            ),
            'snapshot' => $this->aysa_agent8_get_monitoring_rank_tracking_snapshot($project_id),
            'async_job' => $this->aysa_monitoring_keywords_async_output_status($status),
        );
    }

    private function aysa_agent8_apply_monitoring_query_pack_save($project_id, array $monitoring_operation) {
        $project_id = intval($project_id);
        $payload = $this->aysa_monitoring_build_ai_payload();
        $query_pack = isset($monitoring_operation['query_pack']) && is_array($monitoring_operation['query_pack'])
            ? array_values($monitoring_operation['query_pack'])
            : array();
        if ($project_id <= 0 || empty($query_pack)) {
            return array('applied' => false, 'reason' => 'query_pack_required');
        }

        $payload['query_pack'] = $query_pack;
        $result = $this->aysa_broker_request('POST', '/v1/monitoring/ai/query-pack', $payload, 120);
        if (is_wp_error($result)) {
            return array(
                'applied' => false,
                'reason' => 'monitoring_query_pack_save_failed',
                'message' => $this->aysa_humanize_broker_error($result, 'Could not save the monitoring query pack.'),
            );
        }

        $this->aysa_wallet_track_action_usage(
            intval($payload['project_id'] ?? 0),
            'aysa_monitoring_save_ai_query_pack',
            'monitoring_ai_query_pack_save',
            array(
                'module' => 'monitoring_analytics',
                'source' => 'agent8_manual_save',
                'query_count' => count($query_pack),
            ),
            10,
            '/v1/monitoring/ai/query-pack'
        );

        return array(
            'applied' => true,
            'operation' => 'save_monitoring_query_pack',
            'summary' => array(
                'queries' => count($query_pack),
            ),
            'snapshot' => $this->aysa_agent8_get_monitoring_query_packs_snapshot($project_id),
        );
    }

    private function aysa_keyword_v2_finalize_snapshot_contract($project_id, $tab_key, $payload, $items_key = 'rows') {
        $project_id = intval($project_id);
        $tab_key = sanitize_key((string) $tab_key);
        $payload = is_array($payload) ? $payload : array();
        $items_key = is_string($items_key) && $items_key !== '' ? $items_key : 'rows';

        if (!isset($payload['cache_meta']) || !is_array($payload['cache_meta'])) {
            $payload['cache_meta'] = array();
        }

        $payload['project_id'] = $project_id;
        $payload['snapshot_key'] = $tab_key;
        $payload['source'] = $tab_key;
        $payload['refreshed_at'] = $payload['cache_meta']['cached_at'] ?? gmdate('c');
        $payload['cache_meta']['source'] = $payload['cache_meta']['source'] ?? 'live_refresh';
        $payload['cache_meta']['cached_at'] = $payload['cache_meta']['cached_at'] ?? gmdate('c');

        $items = isset($payload[$items_key]) && is_array($payload[$items_key]) ? array_values($payload[$items_key]) : array();
        $payload['items'] = $items;
        if (!isset($payload['count'])) {
            $payload['count'] = count($items);
        }
        if (!isset($payload['quality_flags']) || !is_array($payload['quality_flags'])) {
            $payload['quality_flags'] = array();
        }
        if (!isset($payload['explainability']) || !is_array($payload['explainability'])) {
            $payload['explainability'] = array();
        }
        if (!isset($payload['workspace_handoff']) || !is_array($payload['workspace_handoff'])) {
            $payload['workspace_handoff'] = array();
        }

        return $payload;
    }

    private function aysa_keyword_v2_cache_option_name($project_id, $tab_key) {
        return 'aysa_keyword_v2_cache_' . intval($project_id) . '_' . sanitize_key((string) $tab_key);
    }

    private function aysa_keyword_v2_cache_get($project_id, $tab_key) {
        global $wpdb;

        $project_id = intval($project_id);
        $tab_key = sanitize_key((string) $tab_key);
        if ($project_id <= 0 || $tab_key === '') {
            return null;
        }

        $this->aysa_keyword_v2_ensure_snapshot_table();
        $table = esc_sql($this->aysa_keyword_v2_snapshot_table_name());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $row = $wpdb->get_row(
            $wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT payload_json, updated_at FROM {$table} WHERE project_id = %d AND tab_key = %s LIMIT 1",
                $project_id,
                $tab_key
            ),
            ARRAY_A
        );

        if (!is_array($row) || empty($row['payload_json'])) {
            return null;
        }

        $payload = json_decode((string) $row['payload_json'], true);
        if (!is_array($payload)) {
            return null;
        }

        if (!isset($payload['cache_meta']) || !is_array($payload['cache_meta'])) {
            $payload['cache_meta'] = array();
        }
        $payload['cache_meta']['source'] = 'db_snapshot';
        $payload['cache_meta']['cached_at'] = (string) ($row['updated_at'] ?? ($payload['cache_meta']['cached_at'] ?? ''));
        return $this->aysa_keyword_v2_finalize_snapshot_contract($project_id, $tab_key, $payload, $tab_key === 'keyword_mapping_v2' ? 'clusters' : 'rows');
    }

    private function aysa_keyword_v2_cache_set($project_id, $tab_key, $payload) {
        global $wpdb;

        $project_id = intval($project_id);
        $tab_key = sanitize_key((string) $tab_key);
        if ($project_id <= 0 || $tab_key === '') {
            return;
        }

        $this->aysa_keyword_v2_ensure_snapshot_table();
        $table = esc_sql($this->aysa_keyword_v2_snapshot_table_name());
        $payload_json = wp_json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if (!is_string($payload_json) || $payload_json === '') {
            return;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $wpdb->replace(
            $table,
            array(
                'project_id' => $project_id,
                'tab_key' => $tab_key,
                'payload_json' => $payload_json,
                'updated_at' => current_time('mysql', true),
            ),
            array('%d', '%s', '%s', '%s')
        );
    }

    private function aysa_keyword_v2_cache_delete($project_id, $tab_key) {
        global $wpdb;

        $project_id = intval($project_id);
        $tab_key = sanitize_key((string) $tab_key);
        if ($project_id <= 0 || $tab_key === '') {
            return;
        }

        $this->aysa_keyword_v2_ensure_snapshot_table();
        $table = esc_sql($this->aysa_keyword_v2_snapshot_table_name());
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $wpdb->query(
            $wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "DELETE FROM {$table} WHERE project_id = %d AND tab_key = %s",
                $project_id,
                $tab_key
            )
        );
    }

    private function aysa_keyword_v2_snapshot_table_name() {
        global $wpdb;
        return $wpdb->prefix . 'aysa_keyword_v2_snapshots';
    }

    private function aysa_keyword_v2_ensure_snapshot_table() {
        global $wpdb;

        $table = esc_sql($this->aysa_keyword_v2_snapshot_table_name());
        static $ready = false;
        if ($ready) {
            return;
        }

        $charset_collate = $wpdb->get_charset_collate();
        $sql = "CREATE TABLE IF NOT EXISTS {$table} (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            project_id BIGINT UNSIGNED NOT NULL,
            tab_key VARCHAR(64) NOT NULL,
            payload_json LONGTEXT NOT NULL,
            updated_at DATETIME NOT NULL,
            PRIMARY KEY (id),
            UNIQUE KEY project_tab_unique (project_id, tab_key),
            KEY project_id_idx (project_id),
            KEY updated_at_idx (updated_at)
        ) {$charset_collate};";

        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta($sql);
        $ready = true;
    }

    private function aysa_keyword_discovery_refresh_keyword_suggestions($project_id, $setup_user_id = 0, $track_usage = true) {
        $project_id = intval($project_id);
        $setup_user_id = intval($setup_user_id);
        if ($project_id <= 0) {
            return new WP_Error('invalid_project_id', 'Invalid project id');
        }

        if ($setup_user_id <= 0) {
            $setup_user_id = intval($this->aysa_get_authenticated_external_user_id());
            if ($setup_user_id <= 0) {
                $setup_user_id = get_current_user_id();
            }
        }

        $seed_keywords = $this->aysa_keyword_discovery_get_seed_keywords_with_fallback($setup_user_id);
        if (empty($seed_keywords)) {
            return new WP_Error('missing_seed_keywords', 'No seed keywords found in Setup SEO Profile > Competition.');
        }

        $context = $this->aysa_monitoring_resolve_context('');
        $rows = $this->aysa_keyword_discovery_fetch_keyword_suggestions($project_id, $seed_keywords, $context);
        if (empty($rows)) {
            return new WP_Error('empty_suggestions', 'Keyword suggestions endpoint returned empty data.');
        }

        $normalized = array();
        $seen = array();
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $keyword = trim((string) ($row['keyword'] ?? $row['Keyword'] ?? ''));
            if ($keyword === '') {
                continue;
            }
            $norm = strtolower($keyword);
            if (isset($seen[$norm])) {
                continue;
            }
            $seen[$norm] = true;
            $normalized[] = array(
                'keyword' => $keyword,
                'searchVolume' => $row['search_volume'] ?? ($row['searchVolume'] ?? null),
                'difficulty' => $row['kd'] ?? ($row['difficulty'] ?? null),
                'cpc' => $row['cpc'] ?? null,
                'intent' => $row['intent'] ?? null,
                'intent_probabilities' => $row['intent_probabilities'] ?? array(),
                'item_types' => $row['item_types'] ?? ($row['serp_item_types'] ?? array()),
                'source' => in_array((string) ($row['source'] ?? ''), array('related_keyword', 'keyword_suggestion'), true)
                    ? (string) $row['source']
                    : 'keyword_suggestion',
            );
        }

        if (empty($normalized)) {
            return new WP_Error('empty_normalized_suggestions', 'Keyword suggestions normalization returned empty data.');
        }

        $keyword_texts = array_values(array_unique(array_map(function($row) {
            return trim((string) ($row['keyword'] ?? ''));
        }, $normalized)));
        $metrics = $this->aysa_keyword_discovery_fetch_keyword_metrics($project_id, $keyword_texts, $context);
        if (!empty($metrics)) {
            foreach ($normalized as $i => $row) {
                $key = strtolower(trim((string) ($row['keyword'] ?? '')));
                if ($key === '' || !isset($metrics[$key])) {
                    continue;
                }
                $m = $metrics[$key];
                if (empty($normalized[$i]['intent']) && !empty($m['intent'])) {
                    $normalized[$i]['intent'] = (string) $m['intent'];
                }
                if (!isset($normalized[$i]['searchVolume']) || $normalized[$i]['searchVolume'] === null || $normalized[$i]['searchVolume'] === '') {
                    $normalized[$i]['searchVolume'] = $m['search_volume'] ?? null;
                }
                if (!isset($normalized[$i]['difficulty']) || $normalized[$i]['difficulty'] === null || $normalized[$i]['difficulty'] === '') {
                    $normalized[$i]['difficulty'] = $m['kd'] ?? null;
                }
                if (!isset($normalized[$i]['cpc']) || $normalized[$i]['cpc'] === null || $normalized[$i]['cpc'] === '') {
                    $normalized[$i]['cpc'] = $m['cpc'] ?? null;
                }
            }
        }

        $this->aysa_keyword_discovery_clear_suggestions_rows($project_id);
        $persist = $this->aysa_keyword_discovery_persist_keywords($project_id, $normalized);

        if ($track_usage) {
            $this->aysa_wallet_track_action_usage(
                $project_id,
                'aysa_keyword_discovery_keyword_suggestions_refresh',
                'keyword_discovery_keyword_suggestions_refresh',
                array(
                    'module' => 'keyword_suggestions',
                    'seed_keywords_count' => count($seed_keywords),
                    'rows_count' => count($normalized),
                    'inserted' => intval($persist['inserted'] ?? 0),
                    'updated' => intval($persist['updated'] ?? 0),
                ),
                35,
                '/v1/keywords/suggestions'
            );
        }

        return array(
            'seed_keywords' => $seed_keywords,
            'count' => count($normalized),
            'inserted' => intval($persist['inserted'] ?? 0),
            'updated' => intval($persist['updated'] ?? 0),
            'rows' => $normalized,
        );
    }

    public function handle_keyword_discovery_refresh_background_event($project_id = 0) {
        $project_id = $this->aysa_resolve_installed_project_id(intval($project_id));
        if ($project_id <= 0) {
            return;
        }

        $lock_key = $this->aysa_keyword_discovery_lock_key($project_id);
        if (get_transient($lock_key)) {
            $this->aysa_keyword_discovery_schedule_background_refresh($project_id, 15);
            return;
        }
        set_transient($lock_key, 1, 180);

        try {
            $status = $this->aysa_keyword_discovery_get_status($project_id);
            if (($status['status'] ?? '') !== 'running') {
                return;
            }

            $refresh_kind = strtolower(trim((string) ($status['refresh_kind'] ?? 'gsc_auto')));
            if (!in_array($refresh_kind, array('gsc_auto', 'keyword_suggestions'), true)) {
                $refresh_kind = 'gsc_auto';
            }

            if ($refresh_kind === 'keyword_suggestions') {
                if (!class_exists('Aysa_Auth_Manager')) {
                    require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
                }
                $setup_user_id = intval($this->aysa_get_authenticated_external_user_id());
                if ($setup_user_id <= 0) {
                    $setup_user_id = get_current_user_id();
                }
                $result = $this->aysa_keyword_discovery_refresh_keyword_suggestions($project_id, $setup_user_id, true);
                if (is_wp_error($result)) {
                    throw new Exception($result->get_error_message());
                }
                $status['total'] = intval($result['count'] ?? 0);
                $status['processed'] = intval($result['count'] ?? 0);
                $status['staged'] = intval($result['count'] ?? 0);
                $status['inserted'] = intval($result['inserted'] ?? 0);
                $status['updated'] = intval($result['updated'] ?? 0);
                $status['status'] = 'completed';
                $status['updated_at'] = time();
                $status['finished_at'] = time();
                $status['last_error'] = '';
                $status['metrics_notice'] = '';
                $this->aysa_keyword_discovery_save_status($project_id, $status);
                return;
            }

            $site_host = wp_parse_url(home_url(), PHP_URL_HOST);
            $site_host = is_string($site_host) ? $site_host : '';

            $raw = $this->aysa_broker_request('POST', '/v1/google/keyword-mappings', array(
                'project_id' => $project_id,
                'site_host' => $site_host,
                'days' => 90,
                'limit' => 500,
            ), 60);

            if (is_wp_error($raw)) {
                throw new Exception((string) $raw->get_error_message());
            }
            if (!is_array($raw) || empty($raw['connected'])) {
                $reason = is_array($raw) && !empty($raw['error']) ? (string) $raw['error'] : 'Could not fetch raw GSC keyword mappings.';
                if ($reason === 'reauthorize_required') {
                    $reason = 'Google Search Console needs reconnection.';
                }
                throw new Exception($reason);
            }

            $query_rows = isset($raw['gsc_queries']) && is_array($raw['gsc_queries']) ? $raw['gsc_queries'] : array();
            $mapping_rows = isset($raw['gsc_mappings']) && is_array($raw['gsc_mappings']) ? $raw['gsc_mappings'] : array();

            $keywords = array();
            $keyword_index = array();

            foreach ($query_rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $keyword = trim((string) ($row['query'] ?? $row['keyword'] ?? ''));
                if ($keyword === '') {
                    continue;
                }
                $norm = strtolower($keyword);
                $keywords[] = array(
                    'keyword' => $keyword,
                    'searchVolume' => null,
                    'difficulty' => null,
                    'cpc' => null,
                    'intent' => '',
                    'competition' => '',
                    'traffic' => intval($row['clicks'] ?? 0),
                    'trafficPct' => isset($row['ctr']) ? floatval($row['ctr']) : null,
                    'position' => isset($row['position']) ? floatval($row['position']) : null,
                    'source' => 'gsc_query_only',
                    'impressions' => intval($row['impressions'] ?? 0),
                    'url' => '',
                );
                $keyword_index[$norm] = count($keywords) - 1;
            }

            foreach ($mapping_rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $keyword = trim((string) ($row['query'] ?? $row['keyword'] ?? ''));
                if ($keyword === '') {
                    continue;
                }
                $norm = strtolower($keyword);
                $target_url = trim((string) ($row['page'] ?? $row['url'] ?? ''));
                if (isset($keyword_index[$norm])) {
                    $idx = $keyword_index[$norm];
                    if ($target_url !== '') {
                        $keywords[$idx]['url'] = $target_url;
                    }
                    $keywords[$idx]['source'] = 'gsc';
                    if (intval($row['clicks'] ?? 0) > intval($keywords[$idx]['traffic'] ?? 0)) {
                        $keywords[$idx]['traffic'] = intval($row['clicks'] ?? 0);
                    }
                    if ((intval($row['impressions'] ?? 0) > 0) && empty($keywords[$idx]['impressions'])) {
                        $keywords[$idx]['impressions'] = intval($row['impressions'] ?? 0);
                    }
                    if (!empty($row['position'])) {
                        $keywords[$idx]['position'] = floatval($row['position']);
                    }
                    continue;
                }

                $keywords[] = array(
                    'keyword' => $keyword,
                    'searchVolume' => null,
                    'difficulty' => null,
                    'cpc' => null,
                    'intent' => '',
                    'competition' => '',
                    'traffic' => intval($row['clicks'] ?? 0),
                    'trafficPct' => isset($row['ctr']) ? floatval($row['ctr']) : null,
                    'position' => isset($row['position']) ? floatval($row['position']) : null,
                    'source' => 'gsc',
                    'impressions' => intval($row['impressions'] ?? 0),
                    'url' => $target_url,
                );
                $keyword_index[$norm] = count($keywords) - 1;
            }

            $context = $this->aysa_monitoring_resolve_context('');
            $keyword_texts = array();
            foreach ($keywords as $krow) {
                if (!is_array($krow)) {
                    continue;
                }
                $kw = trim((string) ($krow['keyword'] ?? $krow['Keyword'] ?? ''));
                if ($kw !== '') {
                    $keyword_texts[] = $kw;
                }
            }
            $keyword_texts = array_values(array_unique($keyword_texts));
            $keyword_intents = $this->aysa_keyword_discovery_fetch_keyword_intent_bulk($project_id, $keyword_texts, $context);
            if (!empty($keyword_intents)) {
                foreach ($keywords as $i => $krow) {
                    if (!is_array($krow)) {
                        continue;
                    }
                    $kw = strtolower(trim((string) ($krow['keyword'] ?? $krow['Keyword'] ?? '')));
                    if ($kw === '' || !isset($keyword_intents[$kw])) {
                        continue;
                    }
                    $intent_row = $keyword_intents[$kw];
                    if (!empty($intent_row['intent'])) {
                        $keywords[$i]['intent'] = (string) $intent_row['intent'];
                    }
                    if (isset($intent_row['intent_probabilities']) && is_array($intent_row['intent_probabilities'])) {
                        $keywords[$i]['intent_probabilities'] = $intent_row['intent_probabilities'];
                    }
                    if (!empty($intent_row['intent_source'])) {
                        $keywords[$i]['intent_source'] = (string) $intent_row['intent_source'];
                    }
                    if (!empty($intent_row['intent_updated_at'])) {
                        $keywords[$i]['intent_updated_at'] = (string) $intent_row['intent_updated_at'];
                    }
                }
            }
            $keyword_metrics = $this->aysa_keyword_discovery_fetch_keyword_metrics($project_id, $keyword_texts, $context);

            if (!empty($keyword_metrics)) {
                foreach ($keywords as $i => $krow) {
                    if (!is_array($krow)) {
                        continue;
                    }
                    $kw = strtolower(trim((string) ($krow['keyword'] ?? $krow['Keyword'] ?? '')));
                    if ($kw === '' || !isset($keyword_metrics[$kw])) {
                        continue;
                    }
                    $m = $keyword_metrics[$kw];
                    if (!is_null($m['intent']) && (string) ($krow['intent'] ?? '') === '') {
                        $keywords[$i]['intent'] = (string) $m['intent'];
                    }
                    if (!is_null($m['search_volume'])) {
                        $keywords[$i]['searchVolume'] = intval($m['search_volume']);
                    }
                    if (!is_null($m['kd'])) {
                        $keywords[$i]['difficulty'] = floatval($m['kd']);
                    }
                    if (!is_null($m['cpc'])) {
                        $keywords[$i]['cpc'] = floatval($m['cpc']);
                    }
                    if (!is_null($m['traffic'])) {
                        $keywords[$i]['traffic'] = intval($m['traffic']);
                    }
                    if (!is_null($m['traffic_pct'])) {
                        $keywords[$i]['trafficPct'] = floatval($m['traffic_pct']);
                    }
                }
            }

            // Guardrail: avoid false "completed" refreshes with all metrics empty.
            $rows_with_real_metrics = 0;
            foreach ($keywords as $krow) {
                if (!is_array($krow)) {
                    continue;
                }
                $has_intent = trim((string) ($krow['intent'] ?? '')) !== '';
                $has_volume = isset($krow['searchVolume']) && $krow['searchVolume'] !== null && $krow['searchVolume'] !== '' && $krow['searchVolume'] !== 'N/A';
                $has_kd = isset($krow['difficulty']) && $krow['difficulty'] !== null && $krow['difficulty'] !== '' && $krow['difficulty'] !== 'N/A';
                $has_cpc = isset($krow['cpc']) && $krow['cpc'] !== null && $krow['cpc'] !== '' && $krow['cpc'] !== 'N/A';
                if ($has_intent || $has_volume || $has_kd || $has_cpc) {
                    $rows_with_real_metrics++;
                }
            }
            $metrics_notice = '';
            if (!empty($keywords) && $rows_with_real_metrics === 0) {
                $metrics_notice = 'Keyword ideas were refreshed, but live SEO metrics are not available yet.';
            }

            update_option($this->aysa_keyword_discovery_snapshot_key($project_id), array(
                'run_id' => (string) ($status['run_id'] ?? ''),
                'project_id' => $project_id,
                'fetched_at' => time(),
                'count' => count($keywords),
                'keywords' => $keywords,
            ), false);

            $persist = array('inserted' => 0, 'updated' => 0);
            $apply_to_wp = !empty($status['apply_to_wp']);
            if ($apply_to_wp) {
                $this->aysa_keyword_discovery_clear_gsc_rows($project_id);
                $persist = $this->aysa_keyword_discovery_persist_keywords($project_id, $keywords);
            }
            $status['total'] = count($keywords);
            $status['processed'] = count($keywords);
            $status['staged'] = count($keywords);
            $status['inserted'] = intval($persist['inserted'] ?? 0);
            $status['updated'] = intval($persist['updated'] ?? 0);
            $status['status'] = 'completed';
            $status['updated_at'] = time();
            $status['finished_at'] = time();
            $status['last_error'] = '';
            $status['metrics_notice'] = $metrics_notice;
            $this->aysa_keyword_discovery_save_status($project_id, $status);
            $this->aysa_project_jobs_finalize(
                $project_id,
                $this->aysa_project_jobs_make_worker_ref('keyword_discovery', 'keyword_discovery_refresh', (string) ($status['run_id'] ?? '')),
                'completed',
                array(
                    'total' => intval($status['total'] ?? 0),
                    'inserted' => intval($status['inserted'] ?? 0),
                    'updated' => intval($status['updated'] ?? 0),
                ),
                ''
            );
        } catch (Exception $e) {
            $status = $this->aysa_keyword_discovery_get_status($project_id);
            $status['status'] = 'failed';
            $status['last_error'] = (string) $e->getMessage();
            $status['updated_at'] = time();
            $status['finished_at'] = time();
            $this->aysa_keyword_discovery_save_status($project_id, $status);
            $this->aysa_project_jobs_finalize(
                $project_id,
                $this->aysa_project_jobs_make_worker_ref('keyword_discovery', 'keyword_discovery_refresh', (string) ($status['run_id'] ?? '')),
                'failed',
                null,
                (string) $e->getMessage()
            );
        } finally {
            delete_transient($lock_key);
        }
    }

    /**
     * AJAX handler: cumulative Research summary for Content Strategy dashboard.
     */
    public function handle_get_research_summary() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aysa_content_strategy_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }

        if (!current_user_can('manage_options')) {
            wp_send_json_error('Access denied');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project ID');
            return;
        }

        // Keep Sitemap Competition operationally isolated from the shared Research summary.
        // It has its own AJAX/storage/context flow and should not be preloaded or folded into
        // the fast summary endpoint used by other tabs.
        $requested_scope = isset($_POST['scope']) ? sanitize_key((string) wp_unslash($_POST['scope'])) : '';
        if ($requested_scope === 'sitemap-competition') {
            wp_send_json_success(array(
                'project_id' => $project_id,
                'generated_at' => current_time('mysql'),
                'isolated' => true,
                'message' => 'Sitemap Competition is isolated from the shared Research summary endpoint.',
            ));
            return;
        }

        $summary = array(
            'project_id' => $project_id,
            'generated_at' => current_time('mysql'),
            'content_strategy' => array(
                'total' => 0,
                'generated' => 0,
                'published' => 0,
                'pending' => 0
            ),
            'keyword_discovery' => array(
                'suggested_keywords' => 0,
                'associated_keywords' => 0,
                'associations_total' => 0
            ),
            'competitor' => array(
                'domains' => 0,
                'avg_authority' => 0,
                'gap_keywords' => 0,
                'backlink_domains' => 0
            )
        );

        global $wpdb;
        $table_name = esc_sql($wpdb->prefix . 'aysa_content_strategies');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name));
        if ($table_exists === $table_name) {
            $plan_batch = $this->aysa_agent8_current_research_plan_batch($project_id);
            if ($plan_batch !== '') {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $rows = $wpdb->get_results($wpdb->prepare(
                    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                    "SELECT status, post_id FROM {$table_name} WHERE project_id = %d AND plan_batch = %s",
                    $project_id,
                    $plan_batch
                ), ARRAY_A);
            } else {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $rows = $wpdb->get_results($wpdb->prepare(
                    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                    "SELECT status, post_id FROM {$table_name} WHERE project_id = %d",
                    $project_id
                ), ARRAY_A);
            }

            if (is_array($rows)) {
                $summary['content_strategy']['total'] = count($rows);
                foreach ($rows as $row) {
                    $status = isset($row['status']) ? strtolower((string) $row['status']) : '';
                    $post_id = isset($row['post_id']) ? intval($row['post_id']) : 0;

                    if ($status === 'generated') {
                        $summary['content_strategy']['generated']++;
                    }
                    if ($post_id > 0 && get_post_status($post_id) === 'publish') {
                        $summary['content_strategy']['published']++;
                    }
                }
                $summary['content_strategy']['pending'] = max(
                    0,
                    $summary['content_strategy']['total'] - $summary['content_strategy']['generated']
                );
            }
        }

        // Local-first summary for UI responsiveness.
        // This endpoint is loaded frequently in Content Strategy and must stay fast.
        try {
            $keyword_table = esc_sql($this->aysa_keyword_discovery_table());
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $keyword_table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $keyword_table));
            if ($keyword_table_exists === $keyword_table) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $summary['keyword_discovery']['suggested_keywords'] = intval($wpdb->get_var($wpdb->prepare(
                    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                    "SELECT COUNT(*) FROM {$keyword_table} WHERE project_id = %d",
                    $project_id
                )));
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $summary['competitor']['gap_keywords'] = intval($wpdb->get_var($wpdb->prepare(
                    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                    "SELECT COUNT(*) FROM {$keyword_table} WHERE project_id = %d AND source = %s",
                    $project_id,
                    'competitor_gap'
                )));
            }

            $local_associations = get_option('aysa_keyword_discovery_local_associations_' . intval($project_id), array());
            if (is_array($local_associations)) {
                $summary['keyword_discovery']['associations_total'] = count($local_associations);
                $unique_keywords = array();
                foreach ($local_associations as $assoc) {
                    if (!is_array($assoc)) {
                        continue;
                    }
                    $kw = trim((string) ($assoc['keyword'] ?? ''));
                    if ($kw !== '') {
                        $unique_keywords[strtolower($kw)] = true;
                    }
                }
                $summary['keyword_discovery']['associated_keywords'] = count($unique_keywords);
            }

            // Setup SEO Profile competitors for current authenticated setup user (fallback WP user).
            $setup_user_id = intval($this->aysa_get_authenticated_external_user_id());
            if ($setup_user_id <= 0) {
                $setup_user_id = get_current_user_id();
            }
            $competition = get_option('aysa_wizard_competition_' . intval($setup_user_id), array());
            if (is_array($competition)) {
                $domains = array();
                foreach ($competition as $key => $value) {
                    if (!preg_match('/^competitor\d+$/', (string) $key)) {
                        continue;
                    }
                    $domain = trim((string) $value);
                    if ($domain !== '') {
                        $domains[strtolower($domain)] = true;
                    }
                }
                $summary['competitor']['domains'] = count($domains);
                if ($summary['competitor']['backlink_domains'] <= 0) {
                    $summary['competitor']['backlink_domains'] = count($domains);
                }
            }

            // Reuse local backlink cache summary if present.
            $backlink_cache = get_option('aysa_backlink_summary_' . intval($project_id), array());
            if (is_array($backlink_cache) && isset($backlink_cache['items']) && is_array($backlink_cache['items'])) {
                $domains = array();
                $authorities = array();
                foreach ($backlink_cache['items'] as $item) {
                    if (!is_array($item)) {
                        continue;
                    }
                    $domain = trim((string) ($item['domain'] ?? ''));
                    if ($domain !== '') {
                        $domains[strtolower($domain)] = true;
                    }
                    if (isset($item['authority']) && is_numeric($item['authority'])) {
                        $authorities[] = floatval($item['authority']);
                    }
                }
                if (!empty($domains)) {
                    $summary['competitor']['backlink_domains'] = count($domains);
                }
                if (!empty($authorities)) {
                    $summary['competitor']['avg_authority'] = round(array_sum($authorities) / count($authorities), 1);
                }
            }
        } catch (Exception $e) {
            aysa_debug_log('[AYSA Research Summary local] ' . $e->getMessage());
        }

        wp_send_json_success($summary);
    }

    /**
     * Normalize API responses that may be direct arrays or wrapped in data/items/results.
     */
    private function normalize_aysa_collection_response($response) {
        if (!is_array($response)) {
            return array();
        }

        // If already a list, return directly.
        $is_list = array_keys($response) === range(0, count($response) - 1);
        if ($is_list) {
            return array_values($response);
        }

        if (isset($response['data']) && is_array($response['data'])) {
            $data = $response['data'];
            $data_is_list = array_keys($data) === range(0, count($data) - 1);
            if ($data_is_list) {
                return array_values($data);
            }
        }
        if (isset($response['items']) && is_array($response['items'])) {
            return array_values($response['items']);
        }
        if (isset($response['results']) && is_array($response['results'])) {
            return array_values($response['results']);
        }
        if (isset($response['keywords']) && is_array($response['keywords'])) {
            return array_values($response['keywords']);
        }
        if (isset($response['associations']) && is_array($response['associations'])) {
            return array_values($response['associations']);
        }
        if (isset($response['rows']) && is_array($response['rows'])) {
            return array_values($response['rows']);
        }
        if (isset($response['payload']) && is_array($response['payload'])) {
            $nested_payload = $this->normalize_aysa_collection_response($response['payload']);
            if (!empty($nested_payload)) {
                return $nested_payload;
            }
        }

        // Some APIs wrap collections deeper inside object envelopes.
        foreach ($response as $value) {
            if (!is_array($value)) {
                continue;
            }
            $nested = $this->normalize_aysa_collection_response($value);
            if (!empty($nested)) {
                return $nested;
            }
        }

        return array();
    }

    /**
     * Extract likely domain names from a mixed API payload.
     */
    private function extract_domains_from_mixed_payload($payload) {
        $domains = array();
        $walk = function($node, $key = '') use (&$walk, &$domains) {
            if (is_array($node)) {
                foreach ($node as $k => $value) {
                    $walk($value, is_string($k) ? strtolower($k) : $key);
                }
                return;
            }

            if (!is_string($node)) {
                return;
            }

            $value = trim($node);
            if ($value === '') {
                return;
            }

            $looks_relevant_key = ($key !== '') && (
                strpos($key, 'domain') !== false ||
                strpos($key, 'competitor') !== false ||
                strpos($key, 'url') !== false
            );
            if (!$looks_relevant_key) {
                return;
            }

            $normalized = $this->normalize_domain_string($value);
            if ($normalized !== '') {
                $domains[$normalized] = true;
            }
        };

        $walk($payload, '');
        return array_keys($domains);
    }

    /**
     * Extract numeric values where key name suggests authority-like metrics.
     */
    private function extract_numeric_values_from_mixed_payload($payload, $key_candidates) {
        $numbers = array();
        $candidate_map = array_map('strtolower', (array) $key_candidates);

        $walk = function($node, $key = '') use (&$walk, &$numbers, $candidate_map) {
            if (is_array($node)) {
                foreach ($node as $k => $value) {
                    $walk($value, is_string($k) ? strtolower($k) : $key);
                }
                return;
            }

            if ($key === '' || !in_array($key, $candidate_map, true)) {
                return;
            }

            if (is_numeric($node)) {
                $num = floatval($node);
                if ($num >= 0 && $num <= 100) {
                    $numbers[] = $num;
                }
            }
        };

        $walk($payload, '');
        return $numbers;
    }

    /**
     * Extract unique keyword-like string values from mixed payload.
     */
    private function extract_keywords_from_mixed_payload($payload) {
        $keywords = array();

        $walk = function($node, $key = '') use (&$walk, &$keywords) {
            if (is_array($node)) {
                foreach ($node as $k => $value) {
                    $walk($value, is_string($k) ? strtolower($k) : $key);
                }
                return;
            }

            if (!is_string($node)) {
                return;
            }

            if ($key === '' || strpos($key, 'keyword') === false) {
                return;
            }

            $kw = trim($node);
            if ($kw !== '' && strlen($kw) <= 120) {
                $keywords[strtolower($kw)] = true;
            }
        };

        $walk($payload, '');
        return array_keys($keywords);
    }

    /**
     * Normalize URLs/domains to host string.
     */
    private function normalize_domain_string($value) {
        $candidate = trim((string) $value);
        if ($candidate === '') {
            return '';
        }

        if (!preg_match('#^https?://#i', $candidate)) {
            $candidate = 'https://' . ltrim($candidate, '/');
        }

        $host = wp_parse_url($candidate, PHP_URL_HOST);
        if (!is_string($host) || $host === '') {
            return '';
        }

        $host = strtolower($host);
        if (strpos($host, '.') === false) {
            return '';
        }

        return preg_replace('/^www\./', '', $host);
    }
    
    /**
     * AJAX handler: Get Associations List (for batch processing)
     */
    public function handle_cleanup_get_associations() {
        if (!$this->aysa_guard_destructive_tool_request()) {
            return;
        }

        // Security checks
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Access denied');
            return;
        }
        
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'aysa-cleanup')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        
        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid Project ID');
            return;
        }
        
        try {
            if (!class_exists('Aysa_Auth_Manager')) {
                require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
            }
            
            $auth_manager = Aysa_Auth_Manager::get_instance();
            $associations_url = 'api/Association?projectId=' . $project_id . '&pageSize=10000';
            $associations_response = $auth_manager->make_authenticated_request($associations_url, 'GET');
            
            if ($associations_response === false) {
                wp_send_json_error('Failed to fetch associations from API');
                return;
            }
            
            if (!is_array($associations_response)) {
                wp_send_json_error('Invalid response format from API: ' . gettype($associations_response));
                return;
            }
            
            // Check if response is wrapped or paginated
            if (isset($associations_response['data']) && is_array($associations_response['data'])) {
                $associations_response = $associations_response['data'];
            } elseif (isset($associations_response['items']) && is_array($associations_response['items'])) {
                $associations_response = $associations_response['items'];
            }
            
            // Extract only IDs
            $association_ids = array();
            foreach ($associations_response as $association) {
                $association_id = isset($association['id']) ? $association['id'] : (isset($association['Id']) ? $association['Id'] : null);
                if ($association_id) {
                    $association_ids[] = $association_id;
                }
            }
            
            wp_send_json_success(array(
                'ids' => $association_ids,
                'total' => count($association_ids)
            ));
            
        } catch (Exception $e) {
            aysa_debug_log('[AYSA CLEANUP] Get Associations Exception: ' . $e->getMessage());
            wp_send_json_error('Error: ' . $e->getMessage());
        }
    }
    
    /**
     * AJAX handler: Delete Associations Batch
     */
    public function handle_cleanup_associations_batch() {
        if (!$this->aysa_guard_destructive_tool_request()) {
            return;
        }

        // Security checks
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Access denied');
            return;
        }
        
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'aysa-cleanup')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        
        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid Project ID');
            return;
        }
        
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reviewed release input boundary: the guarded callback preserves structured input only until its field-specific validation or normalizer, before persistence, output, SQL, or a broker request.
        $association_ids = isset($_POST['association_ids']) ? wp_unslash($_POST['association_ids']) : array();
        if (!is_array($association_ids) || empty($association_ids)) {
            wp_send_json_error('No association IDs provided');
            return;
        }
        
        try {
            if (!class_exists('Aysa_Auth_Manager')) {
                require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
            }
            
            $auth_manager = Aysa_Auth_Manager::get_instance();
            $deleted = 0;
            $errors = [];
            
            // Process each association ID
            foreach ($association_ids as $association_id) {
                $association_id = intval($association_id);
                if ($association_id <= 0) {
                    continue;
                }
                
                $delete_url = 'api/Association/' . $association_id . '?projectId=' . $project_id;
                $delete_response = $auth_manager->make_authenticated_request($delete_url, 'DELETE');
                
                if ($delete_response !== false) {
                    $deleted++;
                } else {
                    $errors[] = 'Failed to delete association ID: ' . $association_id;
                }
            }
            
            wp_send_json_success(array(
                'deleted' => $deleted,
                'errors' => $errors,
                'processed' => count($association_ids)
            ));
            
        } catch (Exception $e) {
            aysa_debug_log('[AYSA CLEANUP] Delete Batch Exception: ' . $e->getMessage());
            wp_send_json_error('Error: ' . $e->getMessage());
        }
    }
    
    /**
     * AJAX handler: Cleanup WordPress Meta Fields
     */
    public function handle_cleanup_wordpress_meta() {
        if (!$this->aysa_guard_destructive_tool_request()) {
            return;
        }

        // Security checks
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Access denied');
            return;
        }
        
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'aysa-cleanup')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        
        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid Project ID');
            return;
        }
        
        try {
            global $wpdb;
            
            $total_deleted = 0;
            $errors = [];
            
            // Initialize counters for all meta tables
            $postmeta_deleted = 0;
            $termmeta_deleted = 0;
            $commentmeta_deleted = 0;
            $usermeta_deleted = 0;
            
            // List of known AYSA meta keys to explicitly delete (in addition to pattern matching)
            $known_meta_keys = array(
                'aysa_keyword_data',
                'aysa_keyword',
                'aysa_secondary_keywords',
                'aysa_second_keyword',
                'aysa_meta_title',
                'aysa_meta_description',
                'aysa_meta_keywords',
                'aysa_url_recomandat',
                'aysa_article_topic',
                'aysa_local_specifics',
                '_aysa_seo_title',
                '_aysa_seo_description',
                '_aysa_seo_keywords',
                '_aysa_temp_save_data',
                '_aysa_primary_keyword',
                'aysa_primary_keyword'
            );
            
            // Delete from postmeta (Posts, Pages, Products, etc.)
            // First, delete by pattern (covers most cases)
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $postmeta_deleted_pattern = $wpdb->query(
                "DELETE FROM {$wpdb->postmeta} 
                 WHERE meta_key LIKE 'aysa_%' OR meta_key LIKE '_aysa_%'"
            );
            
            // Log pattern deletion result
            if ($postmeta_deleted_pattern === false) {
                aysa_debug_log('[AYSA CLEANUP] Postmeta pattern query failed: ' . $wpdb->last_error);
            } else {
                aysa_debug_log('[AYSA CLEANUP] Postmeta pattern query result: ' . intval($postmeta_deleted_pattern) . ' rows');
            }
            
            // Then, explicitly delete known meta keys (in case pattern didn't catch them)
            $postmeta_deleted_explicit = 0;
            foreach ($known_meta_keys as $meta_key) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $deleted = $wpdb->query(
                    $wpdb->prepare(
                        "DELETE FROM {$wpdb->postmeta} WHERE meta_key = %s",
                        $meta_key
                    )
                );
                if ($deleted !== false) {
                    $explicit_count = intval($deleted);
                    $postmeta_deleted_explicit += $explicit_count;
                    if ($explicit_count > 0) {
                        aysa_debug_log('[AYSA CLEANUP] Deleted ' . $explicit_count . ' postmeta records for key: ' . $meta_key);
                    }
                } else {
                    aysa_debug_log('[AYSA CLEANUP] Failed to delete postmeta for key: ' . $meta_key . ' - Error: ' . $wpdb->last_error);
                }
            }
            
            // Calculate total - $wpdb->query() returns number of rows affected (0 if none, false on error)
            $postmeta_deleted = ($postmeta_deleted_pattern !== false ? intval($postmeta_deleted_pattern) : 0) + $postmeta_deleted_explicit;
            
            if ($postmeta_deleted_pattern === false) {
                $errors[] = 'Postmeta pattern error: ' . $wpdb->last_error;
            } else {
                $total_deleted += $postmeta_deleted;
                aysa_debug_log('[AYSA CLEANUP] Total postmeta deleted: ' . $postmeta_deleted . ' (pattern: ' . ($postmeta_deleted_pattern !== false ? intval($postmeta_deleted_pattern) : 0) . ', explicit: ' . $postmeta_deleted_explicit . ')');
            }
            
            // Delete from termmeta (Categories, Tags, Custom Taxonomies)
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            if ($wpdb->get_var("SHOW TABLES LIKE '{$wpdb->prefix}termmeta'") == $wpdb->prefix . 'termmeta') {
                $termmeta_deleted_pattern = $wpdb->query(
                    "DELETE FROM {$wpdb->termmeta} 
                     WHERE meta_key LIKE 'aysa_%' OR meta_key LIKE '_aysa_%'"
                );
                
                $termmeta_deleted_explicit = 0;
                foreach ($known_meta_keys as $meta_key) {
                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                    $deleted = $wpdb->query(
                        $wpdb->prepare(
                            "DELETE FROM {$wpdb->termmeta} WHERE meta_key = %s",
                            $meta_key
                        )
                    );
                    if ($deleted !== false) {
                        $termmeta_deleted_explicit += intval($deleted);
                    }
                }
                
                $termmeta_deleted = ($termmeta_deleted_pattern !== false ? intval($termmeta_deleted_pattern) : 0) + $termmeta_deleted_explicit;
                
                if ($termmeta_deleted_pattern === false) {
                    $errors[] = 'Termmeta pattern error: ' . $wpdb->last_error;
                } else {
                    $total_deleted += $termmeta_deleted;
                    aysa_debug_log('[AYSA CLEANUP] Deleted ' . $termmeta_deleted . ' termmeta records (pattern: ' . ($termmeta_deleted_pattern !== false ? intval($termmeta_deleted_pattern) : 0) . ', explicit: ' . $termmeta_deleted_explicit . ')');
                }
            }
            
            // Delete from commentmeta (Comments)
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            if ($wpdb->get_var("SHOW TABLES LIKE '{$wpdb->prefix}commentmeta'") == $wpdb->prefix . 'commentmeta') {
                $commentmeta_deleted_pattern = $wpdb->query(
                    "DELETE FROM {$wpdb->commentmeta} 
                     WHERE meta_key LIKE 'aysa_%' OR meta_key LIKE '_aysa_%'"
                );
                
                $commentmeta_deleted_explicit = 0;
                foreach ($known_meta_keys as $meta_key) {
                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                    $deleted = $wpdb->query(
                        $wpdb->prepare(
                            "DELETE FROM {$wpdb->commentmeta} WHERE meta_key = %s",
                            $meta_key
                        )
                    );
                    if ($deleted !== false) {
                        $commentmeta_deleted_explicit += intval($deleted);
                    }
                }
                
                $commentmeta_deleted = ($commentmeta_deleted_pattern !== false ? intval($commentmeta_deleted_pattern) : 0) + $commentmeta_deleted_explicit;
                
                if ($commentmeta_deleted_pattern === false) {
                    $errors[] = 'Commentmeta pattern error: ' . $wpdb->last_error;
                } else {
                    $total_deleted += $commentmeta_deleted;
                    aysa_debug_log('[AYSA CLEANUP] Deleted ' . $commentmeta_deleted . ' commentmeta records (pattern: ' . ($commentmeta_deleted_pattern !== false ? intval($commentmeta_deleted_pattern) : 0) . ', explicit: ' . $commentmeta_deleted_explicit . ')');
                }
            }
            
            // Delete from usermeta (User meta - if any)
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $usermeta_deleted_pattern = $wpdb->query(
                "DELETE FROM {$wpdb->usermeta} 
                 WHERE meta_key LIKE 'aysa_%' OR meta_key LIKE '_aysa_%'"
            );
            
            $usermeta_deleted_explicit = 0;
            foreach ($known_meta_keys as $meta_key) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $deleted = $wpdb->query(
                    $wpdb->prepare(
                        "DELETE FROM {$wpdb->usermeta} WHERE meta_key = %s",
                        $meta_key
                    )
                );
                if ($deleted !== false) {
                    $usermeta_deleted_explicit += intval($deleted);
                }
            }
            
            $usermeta_deleted = ($usermeta_deleted_pattern !== false ? intval($usermeta_deleted_pattern) : 0) + $usermeta_deleted_explicit;
            
            if ($usermeta_deleted_pattern === false) {
                $errors[] = 'Usermeta pattern error: ' . $wpdb->last_error;
            } else {
                $total_deleted += $usermeta_deleted;
                aysa_debug_log('[AYSA CLEANUP] Deleted ' . $usermeta_deleted . ' usermeta records (pattern: ' . ($usermeta_deleted_pattern !== false ? intval($usermeta_deleted_pattern) : 0) . ', explicit: ' . $usermeta_deleted_explicit . ')');
            }
            
            // Log final results for debugging
            aysa_debug_log('[AYSA CLEANUP] WordPress Meta - Total deleted: ' . $total_deleted);
            aysa_debug_log('[AYSA CLEANUP] WordPress Meta - Breakdown: postmeta=' . intval($postmeta_deleted) . ', termmeta=' . intval($termmeta_deleted) . ', commentmeta=' . intval($commentmeta_deleted) . ', usermeta=' . intval($usermeta_deleted));
            
            wp_send_json_success(array(
                'deleted' => $total_deleted,
                'errors' => $errors,
                'breakdown' => array(
                    'postmeta' => intval($postmeta_deleted),
                    'termmeta' => intval($termmeta_deleted),
                    'commentmeta' => intval($commentmeta_deleted),
                    'usermeta' => intval($usermeta_deleted)
                )
            ));
            
        } catch (Exception $e) {
            aysa_debug_log('[AYSA CLEANUP] WordPress Meta Exception: ' . $e->getMessage());
            wp_send_json_error('Error: ' . $e->getMessage());
        } catch (Error $e) {
            aysa_debug_log('[AYSA CLEANUP] WordPress Meta Fatal Error: ' . $e->getMessage());
            wp_send_json_error('Fatal Error: ' . $e->getMessage());
        }
    }
    
    /**
     * AJAX handler: Cleanup GeneratedSeoTitles
     */
    public function handle_cleanup_generated_titles() {
        if (!$this->aysa_guard_destructive_tool_request()) {
            return;
        }

        // Security checks
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Access denied');
            return;
        }
        
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'aysa-cleanup')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        
        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid Project ID');
            return;
        }
        
        try {
            if (!class_exists('Aysa_Auth_Manager')) {
                require_once AYSA_PLUGIN_DIR . 'includes/class-aysa-auth-manager.php';
            }
            
            $auth_manager = Aysa_Auth_Manager::get_instance();
            
            // Try to get all generated titles for this project
            $titles_url = 'api/SeoTitleGeneration/ByProject/' . $project_id;
            $titles_response = $auth_manager->make_authenticated_request($titles_url, 'GET');
            
            $deleted = 0;
            $errors = [];
            
            if ($titles_response !== false && is_array($titles_response)) {
                // Check if response is wrapped
                if (isset($titles_response['data']) && is_array($titles_response['data'])) {
                    $titles_response = $titles_response['data'];
                }
                
                foreach ($titles_response as $title) {
                    $title_id = isset($title['id']) ? $title['id'] : (isset($title['Id']) ? $title['Id'] : null);
                    if ($title_id) {
                        $delete_url = 'api/SeoTitleGeneration/' . $title_id;
                        $delete_response = $auth_manager->make_authenticated_request($delete_url, 'DELETE');
                        
                        // 204 No Content, true, or any truthy value means success
                        if ($delete_response !== false) {
                            $deleted++;
                        } else {
                            $errors[] = 'Failed to delete title ID: ' . $title_id;
                        }
                    }
                }
            } else {
                // Endpoint doesn't exist or returned no data - this is OK, not an error
                aysa_debug_log('[AYSA CLEANUP] GeneratedSeoTitles endpoint not found or returned no data - this is normal if endpoint doesn\'t exist');
                // Don't add to errors - this is expected if endpoint doesn't exist
                // Return success with 0 deleted
            }
            
            wp_send_json_success(array(
                'deleted' => $deleted,
                'errors' => $errors
            ));
            
        } catch (Exception $e) {
            wp_send_json_error('Error: ' . $e->getMessage());
        }
    }
    
    /**
     * AJAX handler: Cleanup Content Strategies
     */
    public function handle_cleanup_content_strategies() {
        if (!$this->aysa_guard_destructive_tool_request()) {
            return;
        }

        $nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'aysa-cleanup')) {
            wp_send_json_error('Invalid nonce');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Access denied');
            return;
        }
        
        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Project does not match this WordPress installation');
            return;
        }
        
        try {
            global $wpdb;
            
            $table_name = esc_sql($wpdb->prefix . 'aysa_content_strategies');
            $meta_table = esc_sql($this->aysa_missing_page_meta_table());
            
            // Check if table exists
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$table_name}'") == $table_name;
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $meta_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $meta_table)) === $meta_table;

            $deleted = 0;
            $meta_deleted = 0;
            $snapshot_deleted = 0;
            $errors = array();

            if ($meta_exists) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $meta_deleted_result = $wpdb->query(
                    $wpdb->prepare(
                        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                        "DELETE FROM {$meta_table} WHERE project_id = %d",
                        $project_id
                    )
                );
                if ($meta_deleted_result === false) {
                    $errors[] = 'Missing page meta deletion error: ' . $wpdb->last_error;
                    aysa_debug_log('[AYSA CLEANUP] Missing page meta deletion error: ' . $wpdb->last_error);
                } else {
                    $meta_deleted = intval($meta_deleted_result);
                }
            }

            if ($table_exists) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $deleted_result = $wpdb->query(
                    $wpdb->prepare(
                        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                        "DELETE FROM {$table_name} WHERE project_id = %d",
                        $project_id
                    )
                );
                if ($deleted_result === false) {
                    $errors[] = 'Content strategies deletion error: ' . $wpdb->last_error;
                    aysa_debug_log('[AYSA CLEANUP] Content strategies deletion error: ' . $wpdb->last_error);
                } else {
                    $deleted = intval($deleted_result);
                }
            }

            $before_content_snapshot = $this->aysa_keyword_v2_cache_get($project_id, 'content_strategy_v2');
            $before_missing_snapshot = $this->aysa_keyword_v2_cache_get($project_id, 'missing_pages_v2');
            $this->aysa_keyword_v2_cache_delete($project_id, 'content_strategy_v2');
            $this->aysa_keyword_v2_cache_delete($project_id, 'missing_pages_v2');
            if (is_array($before_content_snapshot)) {
                $snapshot_deleted++;
            }
            if (is_array($before_missing_snapshot)) {
                $snapshot_deleted++;
            }

            update_option('aysa_content_strategy_hard_reset_' . $project_id, current_time('mysql'));

            if (!$table_exists && !$meta_exists && $snapshot_deleted === 0) {
                wp_send_json_success(array(
                    'deleted' => 0,
                    'meta_deleted' => 0,
                    'snapshots_deleted' => 0,
                    'errors' => array(),
                    'message' => 'Nothing to clear for this project'
                ));
                return;
            }
            
            wp_send_json_success(array(
                'deleted' => $deleted,
                'meta_deleted' => $meta_deleted,
                'snapshots_deleted' => $snapshot_deleted,
                'errors' => $errors,
                'message' => sprintf(
                    'Cleared %d content strategies, %d meta rows and %d snapshots.',
                    $deleted,
                    $meta_deleted,
                    $snapshot_deleted
                )
            ));
            
        } catch (Exception $e) {
            aysa_debug_log('[AYSA CLEANUP] Content Strategies Exception: ' . $e->getMessage());
            wp_send_json_error('Error: ' . $e->getMessage());
        } catch (Error $e) {
            aysa_debug_log('[AYSA CLEANUP] Content Strategies Fatal Error: ' . $e->getMessage());
            wp_send_json_error('Fatal Error: ' . $e->getMessage());
        }
    }

    /**
     * AJAX handler: Cleanup local project data/caches/queues.
     */
    public function handle_cleanup_local_project_data() {
        if (!$this->aysa_guard_destructive_tool_request()) {
            return;
        }

        if (!current_user_can('manage_options')) {
            wp_send_json_error('Access denied');
            return;
        }

        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'aysa-cleanup')) {
            wp_send_json_error('Invalid nonce');
            return;
        }

        $project_id = $this->aysa_resolve_installed_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
        if ($project_id <= 0) {
            wp_send_json_error('Invalid Project ID');
            return;
        }

        try {
            $deleted = 0;
            $errors = array();
            $breakdown = array(
                'keyword_metrics_deleted' => 0,
                'monitoring_rank_history_deleted' => 0,
                'keyword_v2_snapshots_deleted' => 0,
                'content_strategies_deleted' => 0,
                'missing_page_meta_deleted' => 0,
                'pending_reviews_deleted' => 0,
                'options_deleted' => 0,
                'transients_deleted' => 0,
                'scheduled_events_cleared' => 0,
            );

            $database_breakdown = $this->aysa_reset_local_project_database_state($project_id);
            foreach ($database_breakdown as $key => $rows) {
                if (array_key_exists($key, $breakdown)) {
                    $rows = intval($rows);
                    $breakdown[$key] = $rows;
                    $deleted += $rows;
                }
            }

            $option_keys = array(
                $this->aysa_keyword_discovery_status_key($project_id),
                $this->aysa_keyword_discovery_snapshot_key($project_id),
                $this->get_keyword_discovery_local_associations_option_key($project_id),
                $this->aysa_monitoring_refresh_status_key($project_id),
                $this->aysa_monitoring_refresh_queue_key($project_id),
                'aysa_content_strategy_hard_reset_' . $project_id,
            );
            foreach ($option_keys as $key) {
                if (delete_option($key)) {
                    $breakdown['options_deleted']++;
                }
            }

            $transient_keys = array(
                $this->get_suggested_keywords_cache_key($project_id),
                $this->aysa_keyword_discovery_lock_key($project_id),
                $this->aysa_monitoring_refresh_lock_key($project_id),
            );
            foreach ($transient_keys as $key) {
                if (delete_transient($key)) {
                    $breakdown['transients_deleted']++;
                }
            }

            while (false !== ($ts = wp_next_scheduled('aysa_keyword_discovery_refresh_background_event', array($project_id)))) {
                wp_unschedule_event($ts, 'aysa_keyword_discovery_refresh_background_event', array($project_id));
                $breakdown['scheduled_events_cleared']++;
            }
            while (false !== ($ts = wp_next_scheduled('aysa_monitoring_rank_refresh_background_event', array($project_id)))) {
                wp_unschedule_event($ts, 'aysa_monitoring_rank_refresh_background_event', array($project_id));
                $breakdown['scheduled_events_cleared']++;
            }
            while (false !== ($ts = wp_next_scheduled('aysa_monitoring_rank_refresh_cron_tick', array($project_id)))) {
                wp_unschedule_event($ts, 'aysa_monitoring_rank_refresh_cron_tick', array($project_id));
                $breakdown['scheduled_events_cleared']++;
            }

            delete_option('aysa_setup_completed');
            $this->aysa_mark_setup_reset_forced();

            wp_send_json_success(array(
                'deleted' => $deleted,
                'errors' => $errors,
                'breakdown' => $breakdown,
            ));
        } catch (Exception $e) {
            aysa_debug_log('[AYSA CLEANUP] Local project cleanup exception: ' . $e->getMessage());
            wp_send_json_error('Error: ' . $e->getMessage());
        } catch (Error $e) {
            aysa_debug_log('[AYSA CLEANUP] Local project cleanup fatal: ' . $e->getMessage());
            wp_send_json_error('Fatal Error: ' . $e->getMessage());
        }
    }
}
