<?php
namespace Consolety;

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

/**
 * Admin class for dashboard pages
 */
class Admin {

    private $api;

    public function __construct(Api $api) {
        $this->api = $api;

        // Set no-cache headers for all Consolety AJAX requests BEFORE any output
        // This prevents cache plugins (Breeze, WP Rocket, LiteSpeed, etc.) from caching nonces
        add_action('admin_init', [$this, 'maybe_set_nocache_headers'], 1);
        add_action('wp_ajax_nopriv_consolety', [$this, 'set_nocache_headers']);
        add_filter('nocache_headers', [$this, 'add_nocache_headers'], 10, 1);

        add_action('admin_menu', [$this, 'add_menu']);
        add_action('admin_enqueue_scripts', [$this, 'enqueue_styles']);
        add_action('admin_post_consolety_disconnect', [$this, 'handle_disconnect']);
        add_action('admin_post_consolety_refresh', [$this, 'handle_refresh']);

        // Network AJAX handlers
        add_action('wp_ajax_consolety_fetch_network_partners', [$this, 'ajax_fetch_network_partners']);
        add_action('wp_ajax_consolety_send_connection', [$this, 'ajax_send_connection']);
        add_action('wp_ajax_consolety_respond_connection', [$this, 'ajax_respond_connection']);
        add_action('wp_ajax_consolety_cancel_connection', [$this, 'ajax_cancel_connection']);
        add_action('wp_ajax_consolety_fetch_connections', [$this, 'ajax_fetch_connections']);

        // Points AJAX handlers
        add_action('wp_ajax_consolety_fetch_points_balance', [$this, 'ajax_fetch_points_balance']);
        add_action('wp_ajax_consolety_fetch_points_history', [$this, 'ajax_fetch_points_history']);

        // Activity feed AJAX handler
        add_action('wp_ajax_consolety_fetch_activity', [$this, 'ajax_fetch_activity']);

        // Categories AJAX handler
        add_action('wp_ajax_consolety_get_categories', [$this, 'ajax_get_categories']);

        // Site Profile AJAX handlers
        add_action('wp_ajax_consolety_save_site_profile', [$this, 'ajax_save_site_profile']);
        add_action('wp_ajax_consolety_get_site_profile', [$this, 'ajax_get_site_profile']);

        // Guest Post Requirements AJAX handlers
        add_action('wp_ajax_consolety_get_requirements', [$this, 'ajax_get_requirements']);
        add_action('wp_ajax_consolety_save_requirements', [$this, 'ajax_save_requirements']);

        // Diagnostics AJAX handlers
        add_action('wp_ajax_consolety_run_diagnostics', [$this, 'ajax_run_diagnostics']);
        add_action('wp_ajax_consolety_force_tier_sync', [$this, 'ajax_force_tier_sync']);
        add_action('wp_ajax_consolety_send_heartbeat', [$this, 'ajax_send_heartbeat']);
        add_action('wp_ajax_consolety_clear_caches', [$this, 'ajax_clear_caches']);

        // Admin notice for invalid connection
        add_action('admin_notices', [$this, 'show_connection_notice']);
    }

    /**
     * Get badge counts for admin menu (cached in transient)
     *
     * @return array Badge count data
     */
    private function get_badge_counts() {
        // Only fetch if site is connected
        $token = get_option('consolety_trust_token');
        if (empty($token)) {
            return ['total' => 0, 'guest_posts_total' => 0, 'connections_pending' => 0, 'campaigns_pending_publish' => 0];
        }

        $cached = get_transient('consolety_badge_counts');
        if ($cached !== false) {
            return $cached;
        }

        $response = $this->api->getBadgeCounts();
        if (!empty($response['success']) && !empty($response['data'])) {
            $counts = $response['data'];
            set_transient('consolety_badge_counts', $counts, 5 * MINUTE_IN_SECONDS);
            return $counts;
        }

        // API failed - return zeros and cache briefly to avoid hammering
        $zeros = ['total' => 0, 'guest_posts_total' => 0, 'connections_pending' => 0, 'campaigns_pending_publish' => 0];
        set_transient('consolety_badge_counts', $zeros, 1 * MINUTE_IN_SECONDS);
        return $zeros;
    }

    /**
     * Format a menu label with a badge count
     *
     * @param string $label  The menu label text
     * @param int    $count  The badge count
     * @return string Menu label with optional badge HTML
     */
    private function menu_label_with_badge($label, $count) {
        if ($count > 0) {
            return sprintf(
                '%s <span class="awaiting-mod">%d</span>',
                $label,
                $count
            );
        }
        return $label;
    }

    /**
     * Add admin menu
     */
    public function add_menu() {
        $badges = $this->get_badge_counts();
        $total = (int) ($badges['total'] ?? 0);
        $gpCount = (int) ($badges['guest_posts_total'] ?? 0);
        $netCount = (int) ($badges['connections_pending'] ?? 0);
        $campCount = (int) ($badges['campaigns_pending_publish'] ?? 0);

        add_menu_page(
            'Consolety',
            $this->menu_label_with_badge('Consolety', $total),
            'manage_options',
            'consolety',
            [$this, 'render_dashboard'],
            'dashicons-yes-alt',
            30
        );

        add_submenu_page(
            'consolety',
            __('Dashboard', 'consolety'),
            __('Dashboard', 'consolety'),
            'manage_options',
            'consolety',
            [$this, 'render_dashboard']
        );

        add_submenu_page(
            'consolety',
            __('Network', 'consolety'),
            $this->menu_label_with_badge(__('Network', 'consolety'), $netCount),
            'manage_options',
            'consolety-network',
            [$this, 'render_network']
        );

        add_submenu_page(
            'consolety',
            __('Guest Posts', 'consolety'),
            $this->menu_label_with_badge(__('Guest Posts', 'consolety'), $gpCount),
            'manage_options',
            'consolety-guest-posts',
            [$this, 'render_guest_posts']
        );

        add_submenu_page(
            'consolety',
            __('Campaigns', 'consolety'),
            $this->menu_label_with_badge(__('Campaigns', 'consolety'), $campCount),
            'manage_options',
            'consolety-campaigns',
            [$this, 'render_campaigns']
        );

        // Legacy slug redirect (WAX had consolety-guestposts without hyphen)
        add_action('admin_init', function() {
            // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- reading page slug for legacy redirect, no form submission.
            if (isset($_GET['page']) && sanitize_text_field(wp_unslash($_GET['page'])) === 'consolety-guestposts') {
                wp_safe_redirect(admin_url('admin.php?page=consolety-guest-posts'));
                exit;
            }
        });

        add_submenu_page(
            'consolety',
            __('Diagnostics', 'consolety'),
            __('Diagnostics', 'consolety'),
            'manage_options',
            'consolety-diagnostics',
            [$this, 'render_diagnostics']
        );

        add_submenu_page(
            'consolety',
            __('Account', 'consolety'),
            __('Account', 'consolety'),
            'manage_options',
            'consolety-account',
            [$this, 'render_account']
        );
    }

    /**
     * Enqueue admin styles and scripts
     */
    public function enqueue_styles($hook) {
        // Get current page from query string
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- reading page slug for admin routing, no form submission.
        $current_page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '';

        // Only on Consolety pages (check both hook and page parameter)
        if (strpos($hook, 'consolety') === false && strpos($current_page, 'consolety') === false) {
            return;
        }

        // Ensure Dashicons are loaded
        wp_enqueue_style('dashicons');

        // Load WordPress Media Library for Guest Posts page (Featured Image selector)
        // Use both hook and page parameter check for compatibility across hosting setups
        $isGuestPostsPage = strpos($hook, 'consolety-guest-posts') !== false || $current_page === 'consolety-guest-posts';
        if ($isGuestPostsPage) {
            wp_enqueue_media();
        }

        // Load main admin stylesheet
        wp_enqueue_style(
            'consolety-admin',
            CONSOLETY_PLUGIN_URL . 'admin/css/admin.css',
            [],
            CONSOLETY_VERSION
        );

        // Legacy localized data for backward compatibility
        wp_localize_script('jquery', 'consoletyData', [
            'ajaxUrl' => admin_url('admin-ajax.php'),
            'hubUrl' => CONSOLETY_HUB_URL,
            'siteUrl' => get_site_url(),
            'nonce' => wp_create_nonce('consolety_nonce'),
            'isConnected' => $this->api->isConnected(),
        ]);

        // Dashboard page scripts (support top-level and submenu hook variants)
        if ($hook === 'toplevel_page_consolety' || $current_page === 'consolety') {
            wp_enqueue_script(
                'consolety-dashboard',
                CONSOLETY_PLUGIN_URL . 'admin/js/dashboard.js',
                ['jquery'],
                CONSOLETY_VERSION,
                true
            );

            $localeData = $this->getLocaleData();

            wp_localize_script('consolety-dashboard', 'consoletyDashboard', [
                'ajaxUrl' => admin_url('admin-ajax.php'),
                'nonce' => wp_create_nonce('consolety_nonce'),
                'locale' => $localeData,
                'i18n' => [
                    'noHistory' => __('No transaction history yet. Start connecting with partners!', 'consolety'),
                    'failedToLoad' => __('Failed to load history.', 'consolety'),
                    'activity' => __('Activity', 'consolety'),
                    'points' => __('Points', 'consolety'),
                    'date' => __('Date', 'consolety'),
                    'saving' => __('Saving...', 'consolety'),
                    'saved' => __('Saved!', 'consolety'),
                    'failedToSave' => __('Failed to save', 'consolety'),
                    'connectionError' => __('Connection error', 'consolety'),
                    'saveProfile' => __('Save Profile', 'consolety'),
                    'noActivity' => __('No recent activity. Start connecting with partners!', 'consolety'),
                    'failedToLoadActivity' => __('Failed to load activity.', 'consolety'),
                    'justNow' => __('Just now', 'consolety'),
                ],
            ]);
        }

        // Guest Posts page scripts (use both hook and page parameter for compatibility)
        if ($isGuestPostsPage) {
            // DOMPurify for XSS protection when rendering guest post content
            wp_enqueue_script(
                'dompurify',
                CONSOLETY_PLUGIN_URL . 'admin/vendor/dompurify/purify.min.js',
                [],
                '3.3.2',
                true
            );

            // Strict DOMPurify config: blocks style attrs (CSS overlay attacks), data-attrs, dangerous tags
            wp_add_inline_script('dompurify', 'window.VP_DOMPURIFY_CONFIG={ALLOWED_TAGS:["p","br","h2","h3","h4","strong","b","em","i","u","s","del","ins","mark","sub","sup","a","ul","ol","li","blockquote","pre","code","img","table","thead","tbody","tfoot","tr","th","td","figure","figcaption","hr","span","div"],ALLOWED_ATTR:["href","title","target","rel","src","alt","width","height","colspan","rowspan","class"],ALLOW_DATA_ATTR:false};');

            // Partner selection component (FIRST - guest-posts depends on this)
            wp_enqueue_script(
                'consolety-partner-selection',
                CONSOLETY_PLUGIN_URL . 'admin/js/partner-selection.js',
                ['jquery'],
                CONSOLETY_VERSION,
                true
            );

            // Note: wp_enqueue_media() is called earlier (line 105) which loads media library
            // Don't use 'wp-media' as dependency - it's not a valid WP script handle
            wp_enqueue_script(
                'consolety-guest-posts',
                CONSOLETY_PLUGIN_URL . 'admin/js/guest-posts.js',
                ['jquery', 'dompurify', 'consolety-partner-selection'],
                CONSOLETY_VERSION,
                true
            );

            wp_localize_script('consolety-guest-posts', 'consoletyGuestPosts', [
                'ajaxUrl' => admin_url('admin-ajax.php'),
                'nonce' => wp_create_nonce('consolety_nonce'),
                'i18n' => [
                    'selectFeaturedImage' => __('Select Featured Image', 'consolety'),
                    'useAsFeaturedImage' => __('Use as Featured Image', 'consolety'),
                    'changeImage' => __('Change Image', 'consolety'),
                    'selectImage' => __('Select Image', 'consolety'),
                    'wordCountError' => __('Your post has {current} words but the minimum requirement is {min} words.', 'consolety'),
                    'revisionCounter' => __('Revision {current} of {max}', 'consolety'),
                    'lastRevision' => __('This is your last revision!', 'consolety'),
                    'hostFeedback' => __('Host Feedback:', 'consolety'),
                ],
            ]);

            wp_localize_script('consolety-partner-selection', 'consoletyPartnerSelection', [
                'i18n' => [
                    'aiAllowed' => __('AI content allowed', 'consolety'),
                    'aiContentAllowed' => __('AI content is allowed', 'consolety'),
                    'humanOnly' => __('Human written content only - no AI', 'consolety'),
                    'aiWithEdits' => __('AI content allowed if human edited', 'consolety'),
                    'casinoGambling' => __('Casino/Gambling', 'consolety'),
                    'adult18' => __('Adult/18+', 'consolety'),
                    'cbdDrugs' => __('CBD/Drugs', 'consolety'),
                    'cryptocurrency' => __('Cryptocurrency', 'consolety'),
                    'paydayLoans' => __('Payday Loans', 'consolety'),
                ],
            ]);
        }

        // Network page scripts
        if (strpos($hook, 'consolety-network') !== false || $current_page === 'consolety-network') {
            // Flag-icons removed: using native language names (accessibility best practice)

            wp_enqueue_script(
                'consolety-network',
                CONSOLETY_PLUGIN_URL . 'admin/js/network.js',
                ['jquery'],
                CONSOLETY_VERSION,
                true
            );

            // Pass site profile for match badge calculation
            // Use site_profile option first, fallback to site_info for backward compatibility.
            $site_profile = get_option('consolety_site_profile', []);
            if (empty($site_profile)) {
                $site_profile = get_option('consolety_site_info', []);
            }

            // Build language name map from WP translations for network display
            $langNames = ['en' => 'English']; // en_US default not in translations list
            $wpTranslations = [];
            if (!function_exists('wp_get_available_translations')) {
                $translationInstallFile = ABSPATH . 'wp-admin/includes/translation-install.php';
                if (file_exists($translationInstallFile)) {
                    require_once $translationInstallFile;
                }
            }
            if (function_exists('wp_get_available_translations')) {
                $wpTranslations = wp_get_available_translations();
            }
            foreach ($wpTranslations as $wpLoc => $info) {
                $code = substr($wpLoc, 0, 2);
                if (!isset($langNames[$code]) && isset($info['native_name']) && is_string($info['native_name'])) {
                    $langNames[$code] = $info['native_name'];
                }
            }

            wp_localize_script('consolety-network', 'consoletyNetwork', [
                'ajaxUrl' => admin_url('admin-ajax.php'),
                'nonce' => wp_create_nonce('consolety_nonce'),
                'guestPostsUrl' => admin_url('admin.php?page=consolety-guest-posts'),
                'upgradeUrl' => \Consolety\PageContext::get()->upgradeUrl,
                'isPro' => \Consolety\PointsEconomy::isPro(),
                'langNames' => $langNames,
                'mySite' => [
                    'category_id' => intval($site_profile['category_id'] ?? 0),
                    'subcategory_id' => intval($site_profile['subcategory_id'] ?? 0),
                    'language' => substr(get_locale(), 0, 2),
                ],
                'i18n' => [
                    // Error messages
                    'selfConnection' => __('You cannot connect with yourself.', 'consolety'),
                    'cooldownActive' => __('You must wait before requesting again. This partner rejected your previous request.', 'consolety'),
                    'dailyLimitReached' => __('Daily request limit reached. Try again tomorrow.', 'consolety'),
                    'connectionLimitReached' => __('Connection limit reached (10 active connections).', 'consolety'),
                    'tierMismatch' => __('Traffic tier mismatch. Build more traffic to connect with higher-tier partners.', 'consolety'),
                    'alreadyPending' => __('A request is already pending with this site.', 'consolety'),
                    'alreadyConnected' => __('You are already connected with this site.', 'consolety'),
                    'insufficientCredits' => __('Not enough credits. Host guest posts or wait for daily rewards.', 'consolety'),
                    'reputationTooLow' => __('Your reputation is too low. Build trust by providing quality content.', 'consolety'),
                    'languageMismatch' => __('Language mismatch. Connect with sites in your language.', 'consolety'),
                    'nicheMismatch' => __('Niche not compatible. Connect with related categories.', 'consolety'),
                    // UI labels
                    'confirmReject' => __('Are you sure you want to reject this request?', 'consolety'),
                    'confirmCancel' => __('Cancel this connection request?', 'consolety'),
                    'confirmDisconnect' => __('Disconnect from', 'consolety'),
                    'disconnectWarning' => __('You will no longer be able to exchange guest posts.', 'consolety'),
                    'loading' => __('Loading...', 'consolety'),
                    'sending' => __('Sending...', 'consolety'),
                    'disconnecting' => __('Disconnecting...', 'consolety'),
                    'connect' => __('Connect', 'consolety'),
                    'connectCost' => sprintf(
                        /* translators: %d: connection cost in points */
                        __('%d points - refunded when answered', 'consolety'),
                        intval(\Consolety\PointsEconomy::CONNECTION_COST_FREE)
                    ),
                    'connectFreePro' => __('Free on your Pro plan', 'consolety'),
                    'insufficientUpgradePrompt' => __('Not enough points. Host a guest post to earn more - or upgrade to Pro (EUR 29.99/mo) for free connections and double daily points. Open your plan options now?', 'consolety'),
                    'pending' => __('Pending', 'consolety'),
                    'connected' => __('Connected', 'consolety'),
                    'guestPost' => __('Guest Post', 'consolety'),
                    'disconnect' => __('Disconnect', 'consolety'),
                    'accept' => __('Accept', 'consolety'),
                    'reject' => __('Reject', 'consolety'),
                    'cancel' => __('Cancel', 'consolety'),
                    'noPartnersFound' => __('No partners found', 'consolety'),
                    'noReceivedRequests' => __('No received requests', 'consolety'),
                    'noSentRequests' => __('No sent requests', 'consolety'),
                    'noConnectionsYet' => __('No connections yet', 'consolety'),
                    'wantsToConnect' => __('wants to connect', 'consolety'),
                    'waitingForResponse' => __('waiting for response', 'consolety'),
                    'failedToLoadPartners' => __('Failed to load partners', 'consolety'),
                    'failedToRespond' => __('Failed to respond', 'consolety'),
                    'failedToCancel' => __('Failed to cancel', 'consolety'),
                    'failedToDisconnect' => __('Failed to disconnect', 'consolety'),
                    'failedToSend' => __('Failed to send request. Please try again.', 'consolety'),
                    'networkError' => __('Network error. Please try again.', 'consolety'),
                    'allSubcategories' => __('All Subcategories', 'consolety'),
                    'selectMainFirst' => __('Select a main category first', 'consolety'),
                    'noSubcategories' => __('No subcategories available', 'consolety'),
                    'includingSensitive' => __('Including sensitive', 'consolety'),
                    'clicks' => __('clicks', 'consolety'),
                    'words' => __('words', 'consolety'),
                    'links' => __('links', 'consolety'),
                    'max' => __('Max', 'consolety'),
                    'humanOnly' => __('Human Only', 'consolety'),
                    'aiWithEdits' => __('AI + Edits OK', 'consolety'),
                    'newPartner' => __('New Partner', 'consolety'),
                    'temporarilyUnavailable' => __('Temporarily Unavailable', 'consolety'),
                    'startByShowing' => __('Start by showing interest in partners from the Discover tab.', 'consolety'),
                    'tryAdjusting' => __('Try adjusting your filters or check back later.', 'consolety'),
                    'youNeed' => __('You need', 'consolety'),
                    'creditsButHave' => __('credits but only have', 'consolety'),
                    'moreDays' => __('more days before requesting again.', 'consolety'),
                    'partnerInactive' => __('This partner is no longer active', 'consolety'),
                ],
            ]);
        }

        // Campaigns page scripts
        if (strpos($hook, 'consolety-campaigns') !== false || $current_page === 'consolety-campaigns') {
            // Enable wp_editor() media buttons for WYSIWYG editor
            wp_enqueue_media();

            // DOMPurify for XSS protection
            wp_enqueue_script(
                'dompurify',
                CONSOLETY_PLUGIN_URL . 'admin/vendor/dompurify/purify.min.js',
                [],
                '3.3.2',
                true
            );

            // Strict DOMPurify config (same as guest posts page)
            wp_add_inline_script('dompurify', 'window.VP_DOMPURIFY_CONFIG={ALLOWED_TAGS:["p","br","h2","h3","h4","strong","b","em","i","u","s","del","ins","mark","sub","sup","a","ul","ol","li","blockquote","pre","code","img","table","thead","tbody","tfoot","tr","th","td","figure","figcaption","hr","span","div"],ALLOWED_ATTR:["href","title","target","rel","src","alt","width","height","colspan","rowspan","class"],ALLOW_DATA_ATTR:false};');

            wp_enqueue_script(
                'consolety-campaigns',
                CONSOLETY_PLUGIN_URL . 'admin/js/campaigns.js',
                ['jquery', 'dompurify'],
                CONSOLETY_VERSION,
                true
            );

            // Check if Pro (paying OR trial via shared helper)
            $isPro = false;
            if (function_exists('consolety_is_freemius_pro')) {
                $isPro = consolety_is_freemius_pro();
            }
            if (!$isPro) {
                $siteInfo = get_option('consolety_site_info', []);
                $tier = $siteInfo['subscription_tier'] ?? 'free';
                $paidTiers = ['pro', 'plus', 'max', 'agency'];
                $isPro = in_array($tier, $paidTiers, true);
            }

            // Self-healing: if local cache says Free, refresh from Hub to catch upgrades
            // This handles the case where Freemius SDK never registered but user purchased Pro
            if (!$isPro) {
                $lastRefresh = get_transient('consolety_tier_refresh');
                if (!$lastRefresh) {
                    if (class_exists('\\Consolety\\Plugin')) {
                        $plugin = \Consolety\Plugin::instance();
                        $plugin->refresh_status();
                        $refreshed = get_option('consolety_site_info', []);
                        $newTier = $refreshed['subscription_tier'] ?? 'free';
                        if (in_array($newTier, $paidTiers, true)) {
                            $isPro = true;
                            $tier = $newTier;
                        }
                    }
                    set_transient('consolety_tier_refresh', time(), 300); // Check every 5 min
                }
            }

            // Self-healing: if Freemius says Pro but Hub cache says Free, force sync
            // This catches cases where the initial Freemius hook failed (e.g. missing DB columns)
            if ($isPro && function_exists('consolety_sync_tier_to_hub')) {
                $siteInfo = get_option('consolety_site_info', []);
                $hubTier = $siteInfo['subscription_tier'] ?? 'free';
                if ($hubTier === 'free') {
                    $lastSync = get_transient('vp_tier_sync_attempted');
                    if (!$lastSync) {
                        consolety_sync_tier_to_hub();

                        // Refresh local cache from Hub
                        if (class_exists('\\Consolety\\Plugin')) {
                            $plugin = \Consolety\Plugin::instance();
                            $plugin->refresh_status();
                        }

                        // Check if sync succeeded
                        $refreshed = get_option('consolety_site_info', []);
                        $newTier = $refreshed['subscription_tier'] ?? 'free';

                        // Success: throttle 10 min. Failure: retry in 60s
                        set_transient('vp_tier_sync_attempted', time(), $newTier !== 'free' ? 600 : 60);
                    }
                }
            }

            // Pre-fetch categories for campaign category selector (avoids extra AJAX call)
            $categoriesData = [];
            $categoriesResponse = $this->api->getCategories();
            if (!empty($categoriesResponse['success']) && isset($categoriesResponse['data']['categories'])) {
                $categoriesData = $categoriesResponse['data']['categories'];
            }

            wp_localize_script('consolety-campaigns', 'consoletyCampaigns', [
                'ajaxUrl' => admin_url('admin-ajax.php'),
                'nonce' => wp_create_nonce('consolety_nonce'),
                'isPro' => $isPro,
                'categories' => $categoriesData,
                'wpLocale' => get_locale(),
                'i18n' => [
                    'loading' => __('Loading...', 'consolety'),
                    'creating' => __('Creating...', 'consolety'),
                    'claiming' => __('Claiming...', 'consolety'),
                    'noCampaigns' => __('No campaigns available yet.', 'consolety'),
                    'noMyCampaigns' => __('You haven\'t created any campaigns yet.', 'consolety'),
                    'noMyClaims' => __('You haven\'t claimed any campaigns yet.', 'consolety'),
                    'createFirst' => __('Create your first campaign to start receiving publishers.', 'consolety'),
                    'browseDiscover' => __('Browse the Discover tab to find articles to publish.', 'consolety'),
                    'proRequired' => __('You have reached the campaign limit for your current plan.', 'consolety'),
                    'upgradeNow' => __('View plans', 'consolety'),
                    'confirmWithdraw' => __('Are you sure you want to withdraw this campaign? Your 30 points will be refunded.', 'consolety'),
                    'confirmReject' => __('Are you sure you want to reject this claim? The publisher will be refunded their 5 points.', 'consolety'),
                    'confirmAccept' => __('Accept this claim? The publisher will be able to publish the article on their site within 7 days.', 'consolety'),
                    'titleRequired' => __('Title is required', 'consolety'),
                    'contentRequired' => __('Content must be at least 100 characters', 'consolety'),
                    'claims' => __('claims', 'consolety'),
                    'expiresIn' => __('Expires in', 'consolety'),
                    'days' => __('days', 'consolety'),
                    'expired' => __('Expired', 'consolety'),
                    'withdrawn' => __('Withdrawn', 'consolety'),
                    'published' => __('Published', 'consolety'),
                    'pending' => __('Pending', 'consolety'),
                    'accepted' => __('Accepted', 'consolety'),
                    'rejected' => __('Rejected', 'consolety'),
                    'matchGood' => __('Your site matches the filters', 'consolety'),
                    'matchWarning' => __('Your site may not match all filters', 'consolety'),
                    'clicks' => __('clicks/month', 'consolety'),
                    'selectCategory' => __('Select a category...', 'consolety'),
                    'subcategoryOptional' => __('Subcategory (optional)', 'consolety'),
                    'noSubcategories' => __('No subcategories available', 'consolety'),
                ],
            ]);
        }

        // Diagnostics page scripts
        if (strpos($hook, 'consolety-diagnostics') !== false || $current_page === 'consolety-diagnostics') {
            wp_enqueue_script(
                'consolety-diagnostics',
                CONSOLETY_PLUGIN_URL . 'admin/js/diagnostics.js',
                ['jquery'],
                CONSOLETY_VERSION,
                true
            );

            wp_localize_script('consolety-diagnostics', 'consoletyDiagnostics', [
                'ajaxUrl' => admin_url('admin-ajax.php'),
                'nonce' => wp_create_nonce('consolety_nonce'),
                'i18n' => [
                    'running' => __('Running...', 'consolety'),
                    'failed' => __('Failed', 'consolety'),
                    'tierSynced' => __('Tier synced to Hub', 'consolety'),
                    'heartbeatSent' => __('Heartbeat sent', 'consolety'),
                    'cachesCleared' => __('All caches cleared', 'consolety'),
                    'connectionError' => __('Connection error', 'consolety'),
                ],
            ]);
        }

        // Account page scripts
        if (strpos($hook, 'consolety-account') !== false || $current_page === 'consolety-account') {
            wp_enqueue_script(
                'consolety-account',
                CONSOLETY_PLUGIN_URL . 'admin/js/account.js',
                ['jquery'],
                CONSOLETY_VERSION,
                true
            );
        }
    }

    /**
     * Render dashboard page
     */
    public function render_dashboard() {
        // PageContext handles Hub refresh + all view variables
        include CONSOLETY_PLUGIN_DIR . 'admin/views/dashboard.php';
    }

    /**
     * Render network page
     */
    public function render_network() {
        include CONSOLETY_PLUGIN_DIR . 'admin/views/network.php';
    }

    /**
     * Render guest posts page
     */
    public function render_guest_posts() {
        include CONSOLETY_PLUGIN_DIR . 'admin/views/guest-posts.php';
    }

    /**
     * Render campaigns page
     */
    public function render_campaigns() {
        // Prepare categories for the view (campaigns needs API call)
        $consolety_categories = [];
        if ($this->api->isConnected()) {
            $resp = $this->api->getCategories();
            if (!empty($resp['success']) && isset($resp['data']['categories'])) {
                $consolety_categories = $resp['data']['categories'];
            }
        }
        include CONSOLETY_PLUGIN_DIR . 'admin/views/campaigns.php';
    }

    /**
     * Handle disconnect action
     */
    public function handle_disconnect() {
        if (!current_user_can('manage_options')) {
            wp_die('Unauthorized');
        }

        check_admin_referer('consolety_disconnect');

        $this->api->disconnect();

        wp_safe_redirect(admin_url('admin.php?page=consolety&disconnected=1'));
        exit;
    }

    /**
     * Handle refresh action
     */
    public function handle_refresh() {
        if (!current_user_can('manage_options')) {
            wp_die('Unauthorized');
        }

        check_admin_referer('consolety_refresh');

        $result = $this->api->refreshMetrics();

        // Check for success with data
        if (!empty($result['success']) && isset($result['data'])) {
            $data = $result['data'];

            if (isset($data['metrics'])) {
                update_option('consolety_metrics', $data['metrics']);
            }

            if (isset($data['credits']) || isset($data['credits_reserved'])) {
                $siteInfo = get_option('consolety_site_info', []);
                if (isset($data['credits'])) {
                    $siteInfo['credits'] = $data['credits'];
                }
                if (isset($data['credits_reserved'])) {
                    $siteInfo['credits_reserved'] = $data['credits_reserved'];
                }
                update_option('consolety_site_info', $siteInfo);
            }

            wp_safe_redirect(admin_url('admin.php?page=consolety&refreshed=1'));
            exit;
        }

        // Handle specific error codes from Hub API
        $errorCode = $result['error']['code'] ?? 'UNKNOWN';
        $errorParam = match($errorCode) {
            'GSC_NOT_CONNECTED' => 'gsc_not_connected',
            'GSC_SITE_NOT_FOUND' => 'gsc_site_not_found',
            'GSC_TOKEN_EXPIRED' => 'gsc_token_expired',
            'GSC_ERROR' => 'gsc_error',
            'GSC_QUERY_FAILED' => 'gsc_query_failed',
            default => 'refresh_failed',
        };

        wp_safe_redirect(admin_url('admin.php?page=consolety&error=' . $errorParam));
        exit;
    }

    // ==========================================
    // Network AJAX Handlers
    // ==========================================

    /**
     * AJAX: Fetch network partners
     */
    public function ajax_fetch_network_partners() {
        check_ajax_referer('consolety_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        // Check if connected to Hub first
        if (!$this->api->isConnected()) {
            wp_send_json_error(['message' => 'Not connected to Consolety Hub. Please connect first.']);
        }

        $params = [];
        if (!empty($_POST['search'])) {
            $params['search'] = sanitize_text_field(wp_unslash($_POST['search']));
        }
        if (!empty($_POST['language'])) {
            $params['language'] = sanitize_text_field(wp_unslash($_POST['language']));
        }
        // Category filter
        if (!empty($_POST['category_id'])) {
            $params['category_id'] = absint($_POST['category_id']);
        }
        // Hide sensitive niches filter (default: true)
        $params['hide_sensitive'] = isset($_POST['hide_sensitive']) ? filter_var(wp_unslash($_POST['hide_sensitive']), FILTER_VALIDATE_BOOLEAN) : true;

        $response = $this->api->getNetworkPartners($params);

        if (!empty($response['success'])) {
            wp_send_json_success($response['data'] ?? $response);
        }

        // Extract meaningful error message
        $errorMessage = 'Failed to load partners';
        if (isset($response['error']['message'])) {
            $errorMessage = $response['error']['message'];
        } elseif (isset($response['error']['code'])) {
            $errorMessage = 'Error: ' . $response['error']['code'];
        }

        wp_send_json_error(['message' => $errorMessage]);
    }

    /**
     * AJAX: Send connection request
     */
    public function ajax_send_connection() {
        check_ajax_referer('consolety_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        $targetSiteId = absint($_POST['target_site_id'] ?? 0);
        $message = sanitize_textarea_field(wp_unslash($_POST['message'] ?? ''));
        $connectionType = sanitize_text_field(wp_unslash($_POST['connection_type'] ?? 'guest_post'));
        $idempotencyKey = sanitize_text_field(wp_unslash($_POST['idempotency_key'] ?? ''));

        if (!$targetSiteId) {
            wp_send_json_error(['error' => ['code' => 'INVALID_INPUT', 'message' => 'Invalid site ID']]);
        }

        $response = $this->api->sendConnectionRequest($targetSiteId, $message, $connectionType, $idempotencyKey);

        if (!empty($response['success'])) {
            wp_send_json_success($response['data'] ?? $response);
        }

        // Pass through the complete error structure from Hub API
        wp_send_json_error($response);
    }

    /**
     * AJAX: Respond to connection request
     */
    public function ajax_respond_connection() {
        check_ajax_referer('consolety_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        $connectionId = absint($_POST['connection_id'] ?? 0);
        $action = sanitize_text_field(wp_unslash($_POST['response_action'] ?? ''));

        if (!$connectionId || !in_array($action, ['accept', 'reject'])) {
            wp_send_json_error(['message' => 'Invalid parameters']);
        }

        $response = $this->api->respondToConnection($connectionId, $action);

        if (!empty($response['success'])) {
            delete_transient('consolety_badge_counts');
            wp_send_json_success($response);
        }

        wp_send_json_error($response['error'] ?? ['message' => 'Failed to respond to connection']);
    }

    /**
     * AJAX: Cancel connection request
     */
    public function ajax_cancel_connection() {
        check_ajax_referer('consolety_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        $connectionId = absint($_POST['connection_id'] ?? 0);

        if (!$connectionId) {
            wp_send_json_error(['message' => 'Invalid connection ID']);
        }

        $response = $this->api->cancelConnection($connectionId);

        if (!empty($response['success'])) {
            delete_transient('consolety_badge_counts');
            wp_send_json_success($response);
        }

        wp_send_json_error($response['error'] ?? ['message' => 'Failed to cancel connection']);
    }

    /**
     * AJAX: Fetch all connections
     */
    public function ajax_fetch_connections() {
        check_ajax_referer('consolety_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        $response = $this->api->getConnections();

        if (!empty($response['success'])) {
            wp_send_json_success($response);
        }

        wp_send_json_error($response['error'] ?? ['message' => 'Failed to load connections']);
    }

    // ==========================================
    // Points AJAX Handlers
    // ==========================================

    /**
     * AJAX: Fetch points balance
     */
    public function ajax_fetch_points_balance() {
        check_ajax_referer('consolety_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        $response = $this->api->getPointsBalance();

        if (!empty($response['success'])) {
            // Update local cache
            $siteInfo = get_option('consolety_site_info', []);
            $siteInfo['credits'] = $response['data']['balance'] ?? $response['data']['credits'] ?? 0;
            if (isset($response['data']['credits_reserved'])) {
                $siteInfo['credits_reserved'] = (int) $response['data']['credits_reserved'];
            }
            update_option('consolety_site_info', $siteInfo);

            wp_send_json_success($response['data'] ?? $response);
        }

        wp_send_json_error($response['error'] ?? ['message' => 'Failed to load points balance']);
    }

    /**
     * AJAX: Fetch points transaction history
     */
    public function ajax_fetch_points_history() {
        check_ajax_referer('consolety_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        $limit = isset($_POST['limit']) ? absint($_POST['limit']) : 10;
        $response = $this->api->getPointsHistory(['limit' => $limit]);

        if (!empty($response['success'])) {
            $data = $response['data'] ?? [];
            $transactions = [];

            if (is_array($data)) {
                if (isset($data['transactions']) && is_array($data['transactions'])) {
                    $transactions = $data['transactions'];
                } elseif (isset($data['data']) && is_array($data['data'])) {
                    // Supports paginated-style payloads: { data: [...] }
                    $transactions = $data['data'];
                } elseif (array_values($data) === $data) {
                    // Already a flat transactions list
                    $transactions = $data;
                }
            }

            wp_send_json_success(array_values($transactions));
        }

        wp_send_json_error($response['error'] ?? ['message' => 'Failed to load points history']);
    }

    // ==========================================
    // Activity Feed AJAX Handler
    // ==========================================

    /**
     * AJAX: Fetch recent activity feed for the dashboard
     */
    public function ajax_fetch_activity() {
        check_ajax_referer('consolety_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        $response = $this->api->getActivity();

        if (!empty($response['success'])) {
            $activities = $response['data']['activities'] ?? [];
            wp_send_json_success($activities);
        }

        wp_send_json_error($response['error'] ?? ['message' => 'Failed to load activity']);
    }

    // ==========================================
    // Categories AJAX Handler
    // ==========================================

    /**
     * AJAX: Get categories for filtering
     */
    public function ajax_get_categories() {
        check_ajax_referer('consolety_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        $response = $this->api->getCategories();

        if (!empty($response['success'])) {
            wp_send_json_success($response['data'] ?? []);
        }

        // Fallback categories if API fails
        wp_send_json_success([
            'categories' => [
                [
                    'id' => 1,
                    'name' => 'Business & Finance',
                    'is_sensitive' => false,
                    'children' => [],
                ],
                [
                    'id' => 2,
                    'name' => 'Technology & Software',
                    'is_sensitive' => false,
                    'children' => [],
                ],
                [
                    'id' => 3,
                    'name' => 'Health & Fitness',
                    'is_sensitive' => false,
                    'children' => [],
                ],
            ]
        ]);
    }

    // ==========================================
    // Locale Helper
    // ==========================================

    /**
     * Get locale data from WordPress for language auto-detection.
     * Uses get_locale() + wp_get_available_translations() for native names.
     *
     * @return array {wpLocale: string, languageCode: string, nativeName: string}
     */
    private function getLocaleData(): array {
        $wpLocale = get_locale(); // e.g. 'nl_NL', 'en_US'
        if (empty($wpLocale) || !is_string($wpLocale)) {
            $wpLocale = 'en_US';
        }

        $langCode = strtolower(substr($wpLocale, 0, 2)); // e.g. 'nl', 'en'
        if (!preg_match('/^[a-z]{2}$/', $langCode)) {
            $langCode = 'en';
        }

        // wp_get_available_translations() is not guaranteed to be loaded on all admin requests.
        // Load translation-install.php when needed and gracefully degrade if still unavailable.
        $translations = [];
        if (!function_exists('wp_get_available_translations')) {
            $translationInstallFile = ABSPATH . 'wp-admin/includes/translation-install.php';
            if (file_exists($translationInstallFile)) {
                require_once $translationInstallFile;
            }
        }
        if (function_exists('wp_get_available_translations')) {
            $translations = wp_get_available_translations();
        }

        $nativeName = strtoupper($langCode); // fallback
        if (isset($translations[$wpLocale]['native_name']) && is_string($translations[$wpLocale]['native_name'])) {
            $nativeName = $translations[$wpLocale]['native_name'];
        } elseif ($wpLocale === 'en_US' || $langCode === 'en') {
            $nativeName = 'English';
        }

        return [
            'wpLocale'     => $wpLocale,
            'languageCode' => $langCode,
            'nativeName'   => $nativeName,
        ];
    }

    // ==========================================
    // Site Profile AJAX Handlers
    // ==========================================

    /**
     * AJAX: Save site profile (category, language, bio)
     */
    public function ajax_save_site_profile() {
        check_ajax_referer('consolety_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        $categoryId = absint($_POST['category_id'] ?? 0);
        $subcategoryId = absint($_POST['subcategory_id'] ?? 0);
        $bio = sanitize_textarea_field(wp_unslash($_POST['bio'] ?? ''));

        // Auto-detect language from WordPress locale (authoritative source)
        $language = substr(get_locale(), 0, 2);
        if (empty($language)) {
            $language = 'en'; // ultimate fallback
        }

        // Validate
        if (!$categoryId) {
            wp_send_json_error(['message' => __('Please select a category', 'consolety')]);
        }

        if (strlen($bio) > 150) {
            wp_send_json_error(['message' => __('Bio must be 150 characters or less', 'consolety')]);
        }

        $response = $this->api->updateSiteProfile([
            'category_id' => $categoryId,
            'subcategory_id' => $subcategoryId ?: null,
            'language' => $language,
            'bio' => $bio,
        ]);

        if (!empty($response['success'])) {
            // Also store locally for display
            $siteInfo = get_option('consolety_site_info', []);
            $siteInfo['category_id'] = $categoryId;
            $siteInfo['subcategory_id'] = $subcategoryId ?: null;
            $siteInfo['language'] = $language;
            $siteInfo['bio'] = $bio;
            update_option('consolety_site_info', $siteInfo);

            // Keep a dedicated site_profile option for network UI matching logic.
            update_option('consolety_site_profile', [
                'category_id' => $categoryId,
                'subcategory_id' => $subcategoryId ?: null,
                'language' => $language,
                'bio' => $bio,
            ]);

            wp_send_json_success(['message' => __('Profile saved successfully', 'consolety')]);
        }

        wp_send_json_error($response['error'] ?? ['message' => __('Failed to save profile', 'consolety')]);
    }

    /**
     * AJAX: Get current site profile
     */
    public function ajax_get_site_profile() {
        check_ajax_referer('consolety_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        // First try API
        $response = $this->api->getSiteProfile();

        if (!empty($response['success'])) {
            $data = $response['data'] ?? [];

            // Sync local cached profile for cross-page consistency (dashboard/network).
            $siteInfo = get_option('consolety_site_info', []);
            if (isset($data['category_id'])) {
                $siteInfo['category_id'] = absint($data['category_id']);
            }
            if (array_key_exists('subcategory_id', $data)) {
                $siteInfo['subcategory_id'] = absint($data['subcategory_id'] ?? 0) ?: null;
            }
            if (isset($data['language'])) {
                $siteInfo['language'] = sanitize_text_field($data['language']);
            }
            if (isset($data['bio'])) {
                $siteInfo['bio'] = sanitize_textarea_field($data['bio']);
            }
            update_option('consolety_site_info', $siteInfo);

            update_option('consolety_site_profile', [
                'category_id' => absint($data['category_id'] ?? 0) ?: null,
                'subcategory_id' => absint($data['subcategory_id'] ?? 0) ?: null,
                'language' => sanitize_text_field($data['language'] ?? ''),
                'bio' => sanitize_textarea_field($data['bio'] ?? ''),
            ]);

            wp_send_json_success($data);
        }

        // Fall back to local storage
        $siteInfo = get_option('consolety_site_info', []);
        $siteProfile = get_option('consolety_site_profile', []);
        wp_send_json_success([
            'category_id' => $siteInfo['category_id'] ?? ($siteProfile['category_id'] ?? null),
            'subcategory_id' => $siteInfo['subcategory_id'] ?? ($siteProfile['subcategory_id'] ?? null),
            'language' => $siteInfo['language'] ?? ($siteProfile['language'] ?? null),
            'bio' => $siteInfo['bio'] ?? ($siteProfile['bio'] ?? ''),
        ]);
    }

    // ==========================================
    // Guest Post Requirements AJAX Handlers
    // ==========================================

    /**
     * AJAX: Get guest post requirements
     */
    public function ajax_get_requirements() {
        check_ajax_referer('consolety_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        // First try API
        $response = $this->api->getSiteProfile();

        if (!empty($response['success']) && isset($response['data']['guest_post_requirements'])) {
            wp_send_json_success($response['data']['guest_post_requirements']);
        }

        // Fall back to local storage
        $siteInfo = get_option('consolety_site_info', []);
        $defaults = [
            'min_words' => 500,
            'ai_policy' => 'ai_allowed',
            'max_dofollow_links' => 3,
            'forbidden_niches' => [],
            'custom_note' => '',
        ];

        $requirements = $siteInfo['guest_post_requirements'] ?? $defaults;
        wp_send_json_success(array_merge($defaults, $requirements));
    }

    /**
     * AJAX: Save guest post requirements
     */
    public function ajax_save_requirements() {
        check_ajax_referer('consolety_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        $requirementsJson = sanitize_text_field(wp_unslash($_POST['requirements'] ?? '{}'));
        $requirements = json_decode($requirementsJson, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            wp_send_json_error(['message' => __('Invalid requirements data', 'consolety')]);
        }

        // Validate requirements
        $validMinWords = [500, 800, 1000, 1500, 2000];
        $validAiPolicies = ['ai_allowed', 'human_only', 'ai_with_edits'];
        $validMaxLinks = [1, 2, 3];
        $validNiches = ['casino', 'adult', 'cbd', 'crypto', 'payday'];

        // Sanitize
        $sanitized = [
            'min_words' => in_array((int)($requirements['min_words'] ?? 500), $validMinWords)
                ? (int)$requirements['min_words']
                : 500,
            'ai_policy' => in_array($requirements['ai_policy'] ?? 'ai_allowed', $validAiPolicies)
                ? $requirements['ai_policy']
                : 'ai_allowed',
            'max_dofollow_links' => in_array((int)($requirements['max_dofollow_links'] ?? 3), $validMaxLinks)
                ? (int)$requirements['max_dofollow_links']
                : 3,
            'forbidden_niches' => array_filter(
                (array)($requirements['forbidden_niches'] ?? []),
                fn($n) => in_array($n, $validNiches)
            ),
            'custom_note' => substr(sanitize_textarea_field($requirements['custom_note'] ?? ''), 0, 200),
        ];

        // Send to Hub API
        $response = $this->api->updateSiteProfile([
            'guest_post_requirements' => $sanitized,
        ]);

        if (!empty($response['success'])) {
            // Also store locally
            $siteInfo = get_option('consolety_site_info', []);
            $siteInfo['guest_post_requirements'] = $sanitized;
            update_option('consolety_site_info', $siteInfo);

            wp_send_json_success(['message' => __('Requirements saved successfully', 'consolety')]);
        }

        wp_send_json_error($response['error'] ?? ['message' => __('Failed to save requirements', 'consolety')]);
    }

    // ==========================================
    // Connection Notice
    // ==========================================

    /**
     * Show admin notice when connection is invalid
     */
    public function show_connection_notice() {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- reading page slug for admin notice display, no form submission.
        if (!isset($_GET['page']) || strpos(sanitize_text_field(wp_unslash($_GET['page'])), 'consolety') !== 0) {
            return;
        }

        if (get_option('consolety_connection_invalid')) {
            echo wp_kses_post(
                '<div class="notice notice-error is-dismissible">' .
                '<p><strong>Consolety:</strong> ' . esc_html__('Connection could not be verified.', 'consolety') . ' ' .
                '<a href="' . esc_url(admin_url('admin.php?page=consolety')) . '">' . esc_html__('Please reconnect', 'consolety') . '</a>.</p>' .
                '</div>'
            );
        }
    }

    // ==========================================
    // Cache Control
    // ==========================================

    /**
     * Check if current request is a Consolety AJAX request and set no-cache headers
     * Called early on admin_init to prevent any caching
     */
    public function maybe_set_nocache_headers() {
        if (!wp_doing_ajax()) {
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- checking action prefix for cache-control headers, no form submission.
        $action = isset($_REQUEST['action']) ? sanitize_text_field(wp_unslash($_REQUEST['action'])) : '';
        if (strpos($action, 'consolety_') === 0) {
            $this->set_nocache_headers();
        }
    }

    /**
     * Set no-cache headers for AJAX responses
     * Prevents cache plugins from caching nonces and dynamic data
     */
    public function set_nocache_headers() {
        if (!headers_sent()) {
            // WordPress core no-cache headers
            nocache_headers();

            // Extra headers for aggressive cache plugins
            header('Cache-Control: no-cache, no-store, must-revalidate, max-age=0', true);
            header('Pragma: no-cache', true);
            header('Expires: Wed, 11 Jan 1984 05:00:00 GMT', true);

            // Plugin-specific cache bypass headers
            header('X-Breeze-Cache-Control: no-cache', true);  // Breeze
            header('X-LiteSpeed-Cache-Control: no-cache', true);  // LiteSpeed
            header('CDN-Cache-Control: no-store', true);  // Cloudflare/CDNs
            header('Surrogate-Control: no-store', true);  // Varnish/Fastly
        }
    }

    /**
     * Add extra no-cache headers for aggressive cache plugins
     */
    public function add_nocache_headers($headers) {
        // Check if this is a Consolety AJAX request
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- checking action prefix for cache-control headers, no form submission.
        $action = isset($_REQUEST['action']) ? sanitize_text_field(wp_unslash($_REQUEST['action'])) : '';
        if (strpos($action, 'consolety_') === 0) {
            $headers['Cache-Control'] = 'no-cache, no-store, must-revalidate, max-age=0';
            $headers['Pragma'] = 'no-cache';
            $headers['Expires'] = 'Wed, 11 Jan 1984 05:00:00 GMT';
            // Breeze-specific header
            $headers['X-Breeze-Cache-Control'] = 'no-cache';
            // Cloudflare
            $headers['CDN-Cache-Control'] = 'no-store';
        }
        return $headers;
    }

    // ==========================================
    // Diagnostics
    // ==========================================

    /**
     * Render diagnostics page
     */
    public function render_diagnostics() {
        include CONSOLETY_PLUGIN_DIR . 'admin/views/diagnostics.php';
    }

    /**
     * Render account page
     */
    public function render_account() {
        // PageContext handles Hub refresh + all view variables
        include CONSOLETY_PLUGIN_DIR . 'admin/views/account.php';
    }

    /**
     * AJAX: Run full diagnostics check
     */
    public function ajax_run_diagnostics() {
        check_ajax_referer('consolety_nonce', 'nonce');
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        $diagnostics = [];

        // Token present
        $token = get_option('consolety_trust_token', '');
        $diagnostics['token_present'] = !empty($token);

        // Hub reachable -- ping health endpoint
        $hubReachable = false;
        $hubLatency = null;
        $hubError = null;
        $start = microtime(true);
        $healthResponse = wp_remote_get(CONSOLETY_HUB_URL . '/api/v1/health', [
            'timeout' => 10,
            'sslverify' => true,
        ]);
        $hubLatency = round((microtime(true) - $start) * 1000);

        if (!is_wp_error($healthResponse)) {
            $httpCode = wp_remote_retrieve_response_code($healthResponse);
            $hubReachable = ($httpCode >= 200 && $httpCode < 400);
            if (!$hubReachable) {
                $hubError = 'HTTP ' . $httpCode;
            }
        } else {
            $hubError = $healthResponse->get_error_message();
        }
        $diagnostics['hub_reachable'] = $hubReachable;
        $diagnostics['hub_latency_ms'] = $hubLatency;
        $diagnostics['hub_error'] = $hubError;

        // Last heartbeat
        $diagnostics['last_heartbeat'] = get_option('consolety_last_heartbeat', null);

        // Tier info
        $siteInfo = get_option('consolety_site_info', []);
        $hubTier = $siteInfo['subscription_tier'] ?? ($siteInfo['tier'] ?? 'unknown');

        $freemiusTier = 'unknown';
        if (function_exists('consolety_is_freemius_pro')) {
            try {
                $freemiusTier = consolety_is_freemius_pro() ? 'pro' : 'free';
            } catch (\Throwable $e) {
                $freemiusTier = 'error';
            }
        } else {
            $freemiusTier = 'sdk_not_loaded';
        }
        $diagnostics['hub_tier'] = $hubTier;
        $diagnostics['freemius_tier'] = $freemiusTier;
        $diagnostics['tier_mismatch'] = (
            $hubTier !== 'unknown' &&
            $freemiusTier !== 'unknown' &&
            $freemiusTier !== 'sdk_not_loaded' &&
            strpos($freemiusTier, 'error') !== 0 &&
            $hubTier !== $freemiusTier
        );

        // Versions
        $diagnostics['plugin_version'] = CONSOLETY_VERSION;
        $diagnostics['wp_version'] = get_bloginfo('version');
        $diagnostics['php_version'] = PHP_VERSION;

        // Connection status
        $diagnostics['connection_invalid'] = (bool) get_option('consolety_connection_invalid', false);
        $diagnostics['credits'] = $siteInfo['credits'] ?? 0;
        $diagnostics['site_id'] = get_option('consolety_site_id', 'none');

        // Last API error
        $diagnostics['last_api_error'] = get_transient('consolety_last_api_error') ?: null;

        // Deployment integrity -- detect corrupt/partial uploads
        $mainFile = defined('CONSOLETY_PLUGIN_DIR')
            ? CONSOLETY_PLUGIN_DIR . 'consolety.php'
            : '';
        $diagnostics['plugin_file_hash'] = $mainFile && file_exists($mainFile)
            ? md5_file($mainFile)
            : 'N/A';

        // Freemius SDK loaded?
        $sdkPath = defined('CONSOLETY_PLUGIN_DIR')
            ? CONSOLETY_PLUGIN_DIR . 'vendor/freemius/start.php'
            : '';
        $diagnostics['freemius_loaded'] = $sdkPath && file_exists($sdkPath);

        // Active VP submenu slugs -- debug menu drift between sites
        global $submenu;
        $vpMenus = $submenu['consolety'] ?? [];
        $diagnostics['active_menu_slugs'] = array_values(array_map(function($item) {
            return $item[2] ?? '';
        }, $vpMenus));

        wp_send_json_success($diagnostics);
    }

    /**
     * AJAX: Force tier sync to Hub
     */
    public function ajax_force_tier_sync() {
        check_ajax_referer('consolety_nonce', 'nonce');
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        // Clear the sync throttle so it actually fires
        delete_transient('vp_tier_sync_attempted');

        if (function_exists('consolety_sync_tier_to_hub')) {
            consolety_sync_tier_to_hub();
        }

        $siteInfo = get_option('consolety_site_info', []);
        $newTier = $siteInfo['subscription_tier'] ?? ($siteInfo['tier'] ?? 'unknown');

        wp_send_json_success([
            'message' => 'Tier sync completed',
            'tier' => $newTier,
        ]);
    }

    /**
     * AJAX: Manually send heartbeat
     */
    public function ajax_send_heartbeat() {
        check_ajax_referer('consolety_nonce', 'nonce');
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        $result = $this->api->sendHeartbeat();

        if (!empty($result['success'])) {
            update_option('consolety_last_heartbeat', current_time('mysql'));
            wp_send_json_success([
                'message' => 'Heartbeat sent successfully',
                'timestamp' => current_time('mysql'),
            ]);
        }

        wp_send_json_error([
            'message' => 'Heartbeat failed: ' . ($result['error']['message'] ?? $result['message'] ?? 'Unknown error'),
        ]);
    }

    /**
     * AJAX: Clear all VP caches
     */
    public function ajax_clear_caches() {
        check_ajax_referer('consolety_nonce', 'nonce');
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        // Clear tier sync throttle
        delete_transient('vp_tier_sync_attempted');

        // Clear all VP transients
        global $wpdb;
        $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $wpdb->prepare(
                "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
                '_transient_consolety_%',
                '_transient_timeout_consolety_%'
            )
        );

        // Clear health cache if method exists
        if (method_exists($this->api, 'clearHealthCache')) {
            $this->api->clearHealthCache();
        }

        wp_send_json_success(['message' => 'All caches cleared']);
    }

}
