# thinkrank/1.10.0/includes/seo/class-analytics-manager.php

ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console &amp; Local SEO, version 1.10.0. 1,233 lines.

- Page: https://pluginprobe.com/plugins/thinkrank/1.10.0/code/includes/seo/class-analytics-manager.php
- Raw: https://pluginprobe.com/plugins/thinkrank/1.10.0/raw/includes/seo/class-analytics-manager.php
- Modified: 2026-05-05T11:07:32+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/thinkrank/1.10.0/code/includes/seo/class-analytics-manager.php#L10-L20`.

```php
<?php

/**
 * Analytics Manager Class
 *
 * Coordinates Google API integrations for SEO analytics data collection,
 * processing, and AI-powered insights generation. Manages Google Analytics,
 * Search Console, and PageSpeed data with intelligent caching and rate limiting.
 *
 * @package ThinkRank
 * @subpackage SEO
 * @since 1.0.0
 */

declare(strict_types=1);

namespace ThinkRank\SEO;

use ThinkRank\Core\Settings_Manager;
use ThinkRank\Integrations\Google_Analytics_Client;
use ThinkRank\Integrations\Google_Search_Console_Client;
use ThinkRank\Integrations\Google_PageSpeed_Client;
use ThinkRank\Integrations\Google_Search_Analytics_Client;

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

/**
 * Analytics Manager Class
 *
 * Single Responsibility: Coordinate Google API data collection and processing
 * Following ThinkRank manager patterns from AI_Manager and Performance_Monitoring_Manager
 *
 * @since 1.0.0
 */
class Analytics_Manager {

    /**
     * Google Client ID
     * @var string
     */
    private const THINKRANK_GOOGLE_CLIENT_ID = '435184728932-8urjh1ah43490lu32se135o4mj123s84.apps.googleusercontent.com';

    /**
     * Settings Manager instance
     *
     * @var Settings_Manager
     */
    private Settings_Manager $settings_manager;

    /**
     * Google Analytics client
     *
     * @var Google_Analytics_Client|null
     */
    private ?Google_Analytics_Client $analytics_client = null;

    /**
     * Google Search Console client
     *
     * @var Google_Search_Console_Client|null
     */
    private ?Google_Search_Console_Client $search_console_client = null;

    /**
     * Google Search Analytics client
     *
     * @var Google_Search_Analytics_Client|null
     */
    private ?Google_Search_Analytics_Client $search_analytics_client = null;

    /**
     * Google PageSpeed client
     *
     * @var Google_PageSpeed_Client|null
     */
    private ?Google_PageSpeed_Client $pagespeed_client = null;

    /**
     * Cache duration in seconds
     *
     * @var int
     */
    private int $cache_duration;

    /**
     * Static flag to prevent multiple token refreshes in the same request
     *
     * @var bool
     */
    private static bool $token_refreshed_this_request = false;

    /**
     * Constructor
     *
     * @param Settings_Manager|null $settings_manager Settings manager instance
     */
    public function __construct(?Settings_Manager $settings_manager = null) {
        $this->settings_manager = $settings_manager ?? new Settings_Manager();
        // Pro: daily refresh (86400s), Free: 3-day refresh (259200s)
        $this->cache_duration = defined('THINKRANK_PRO_VERSION') ? 86400 : 259200;

        // Clear any existing cached insights to ensure new logic takes effect
        $this->clear_insights_cache();
    }

    /**
     * Initialize Analytics Manager
     * Following ThinkRank init patterns
     *
     * @return void
     */
    public function init(): void {
        // Register custom cron interval (45 minutes)
        add_filter('cron_schedules', [$this, 'add_cron_intervals']);

        // Initialize Google API clients
        add_action('init', [$this, 'initialize_clients']);

        // Initialize token refresh scheduling
        add_action('init', [$this, 'init_token_refresh']);

        // Cron hook for token refresh
        add_action('thinkrank_google_token_refresh', [$this, 'refresh_access_token_cron']);

        // Schedule cache cleanup
        add_action('thinkrank_daily_cleanup', [$this, 'cleanup_cache']);

        // Cleanup cron on plugin deactivation
        register_deactivation_hook(THINKRANK_PLUGIN_FILE, [__CLASS__, 'deactivation_cleanup']);
    }

    /**
     * Add custom cron intervals
     *
     * @param array $schedules Existing cron schedules
     * @return array Modified cron schedules
     */
    public function add_cron_intervals(array $schedules): array {
        $schedules['thinkrank_45min'] = [
            'interval' => 2700, // 45 minutes in seconds
            'display'  => __('Every 45 Minutes', 'thinkrank')
        ];
        return $schedules;
    }

    /**
     * Clean up cron events on plugin deactivation
     *
     * @return void
     */
    public static function deactivation_cleanup(): void {
        $timestamp = wp_next_scheduled('thinkrank_google_token_refresh');
        if ($timestamp) {
            wp_unschedule_event($timestamp, 'thinkrank_google_token_refresh');
        }
    }

    /**
     * Get the initialized Search Console client
     *
     * @return Google_Search_Console_Client|null
     */
    public function get_search_console_client(): ?Google_Search_Console_Client {
        if (!$this->search_console_client) {
            $this->initialize_clients();
        }
        return $this->search_console_client;
    }

    /**
     * Get the configured Search Console property URL
     *
     * @return string
     */
    public function get_property_url(): string {
        return $this->get_setting('search_console_property', get_site_url());
    }

    /**
     * Initialize Google API clients
     * Following AI_Manager client initialization pattern
     *
     * @return void
     */
    public function initialize_clients(): void {
        try {
            // Refresh token if needed (non-forced, checks expiration)
            $this->refresh_access_token();

            // Initialize Search Console client
            $gsc_api_key = $this->get_setting('google_search_console_api_key');
            $access_token = $this->get_setting('google_access_token');

            $timeout = (int) $this->get_setting('api_timeout', 30);
            $this->search_console_client = new Google_Search_Console_Client(
                $gsc_api_key ?: '',
                $timeout,
                !empty($access_token) ? $access_token : null
            );

            // Initialize Search Analytics client
            $this->search_analytics_client = new Google_Search_Analytics_Client(
                $gsc_api_key ?: '',
                $timeout,
                !empty($access_token) ? $access_token : null
            );

            // Initialize PageSpeed client with OAuth token (same token as Search Console)
            if (!empty($access_token)) {
                $timeout = (int) $this->get_setting('api_timeout', 30);
                $this->pagespeed_client = new Google_PageSpeed_Client('', $timeout, $access_token);
            }
        } catch (\Exception $e) {
            error_log('ThinkRank Analytics Init Error: ' . $e->getMessage());
        }
    }

    /**
     * Initialize token refresh scheduling
     * Also migrates old absolute-timestamp expires_in values to relative seconds
     *
     * @return void
     */
    public function init_token_refresh(): void {
        $access_token = $this->get_setting('google_access_token');
        $refresh_token = $this->get_setting('google_refresh_token');

        if (empty($access_token) || empty($refresh_token)) {
            return;
        }

        // Migrate old expires_in values stored as absolute timestamps
        $this->maybe_migrate_expires_in();

        // Schedule recurring hourly cron for token refresh
        $this->schedule_token_refresh();
    }

    /**
     * Migrate old expires_in values from absolute timestamps to relative seconds
     *
     * Old callback.php stored expires_in as time() + token->expires_in (e.g., 1771330205).
     * New behavior stores raw seconds from Google (e.g., 3599).
     *
     * @return void
     */
    private function maybe_migrate_expires_in(): void {
        $expires_in = (int) $this->get_setting('google_token_expires_in');
        $created = (int) $this->get_setting('google_token_created');

        // Google tokens expire in 3600 seconds max. If stored value is > 86400,
        // it's almost certainly the old absolute timestamp format.
        if ($expires_in > 86400 && $created > 0) {
            $relative = $expires_in - $created;
            if ($relative > 0 && $relative <= 7200) {
                // Valid relative value, save the corrected value
                $this->settings_manager->update_settings([
                    'google_token_expires_in' => $relative
                ], 'integrations');
            } else {
                // Can't reliably compute, default to standard 3600
                $this->settings_manager->update_settings([
                    'google_token_expires_in' => 3600
                ], 'integrations');
            }
        }
    }

    /**
     * Schedule recurring cron for token refresh (every 45 minutes)
     *
     * Uses WP recurring cron instead of single events for reliability.
     * The cron callback checks expiration and only refreshes when needed.
     * Using 45-minute interval ensures the cron always fires before
     * Google's ~60-minute token expiry window.
     *
     * @return void
     */
    public function schedule_token_refresh(): void {
        $next = wp_next_scheduled('thinkrank_google_token_refresh');

        // If already scheduled with the old 'hourly' interval, reschedule with 45min
        if ($next) {
            // Check if it's using the old interval by looking at the schedule
            $crons = _get_cron_array();
            foreach ($crons as $timestamp => $cron_hooks) {
                if (isset($cron_hooks['thinkrank_google_token_refresh'])) {
                    foreach ($cron_hooks['thinkrank_google_token_refresh'] as $hash => $args) {
                        if (($args['schedule'] ?? '') === 'hourly') {
                            // Remove old hourly schedule and re-add with 45min
                            wp_unschedule_event($timestamp, 'thinkrank_google_token_refresh');
                            $next = false; // Will be rescheduled below
                        }
                    }
                    break;
                }
            }
        }

        if (!$next) {
            wp_schedule_event(time(), 'thinkrank_45min', 'thinkrank_google_token_refresh');
        }
    }

    /**
     * Cron callback for token refresh
     * Called every 45 minutes; only refreshes if token is expired or expiring soon.
     *
     * @return void
     */
    public function refresh_access_token_cron(): void {
        $this->refresh_access_token();
    }

    /**
     * Ensure the Google access token is fresh before making API calls.
     *
     * This is a static convenience method that can be called from any endpoint
     * (including the Pro plugin) before making Google API requests.
     * Uses a per-request flag to avoid redundant refreshes when multiple
     * endpoints are called in the same HTTP request.
     *
     * @since 1.6.0
     * @return void
     */
    public static function ensure_fresh_token(): void {
        // Only refresh once per HTTP request to avoid parallel race conditions
        if (self::$token_refreshed_this_request) {
            return;
        }

        $manager = new self();
        $manager->refresh_access_token();
        self::$token_refreshed_this_request = true;
    }

    /**
     * Refresh OAuth access token if expired or expiring soon
     *
     * @since 1.5.0
     * @param bool $force Force refresh even if not expired
     * @return void
     */
    public function refresh_access_token(bool $force = false): void {
        $refresh_token = $this->get_setting('google_refresh_token');

        // If no refresh token, we can't refresh
        if (empty($refresh_token)) {
            return;
        }

        $expires_in = (int) $this->get_setting('google_token_expires_in');
        $created = (int) $this->get_setting('google_token_created');
        $current_time = time();

        // Calculate absolute expiration time (created + relative seconds)
        $expiration_time = $created + $expires_in;

        // Refresh if forced, expired, or expiring within 5 minutes (300 seconds)
        if ($force || $current_time >= ($expiration_time - 300)) {

            $api_url = 'https://api.thinkrank.ai/v1/callback.php';

            $response = wp_remote_post($api_url, [
                'body' => [
                    'type' => 'google_analytics',
                    'refresh_token' => $refresh_token,
                    'client_id' => self::THINKRANK_GOOGLE_CLIENT_ID
                ],
                'timeout' => 30
            ]);

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

            $body = wp_remote_retrieve_body($response);
            $data = json_decode($body, true);

            if (empty($data['access_token'])) {
                return;
            }

            // Update settings with new token data
            $this->settings_manager->update_settings([
                'google_access_token' => $data['access_token'],
                'google_token_created' => $current_time,
                'google_token_expires_in' => (int) ($data['expires_in'] ?? 3600)
            ], 'integrations');

            // Also update refresh token if a new one was returned
            if (!empty($data['refresh_token'])) {
                $this->settings_manager->update_settings([
                    'google_refresh_token' => $data['refresh_token']
                ], 'integrations');
            }
        }
    }

    /**
     * Test all Google API connections
     * Following ThinkRank test_connection patterns
     *
     * @return array Connection test results
     */
    public function test_connections(): array {
        $results = [
            'google_analytics' => ['status' => 'not_configured'],
            'search_console' => ['status' => 'not_configured'],
            'pagespeed' => ['status' => 'not_configured']
        ];

        // Test Google Analytics connection
        if ($this->analytics_client) {
            try {
                $test_result = $this->analytics_client->test_connection();
                $results['google_analytics'] = [
                    'status' => $test_result['success'] ? 'connected' : 'error',
                    'message' => $test_result['message'],
                    'details' => $test_result
                ];
            } catch (\Exception $e) {
                $results['google_analytics'] = [
                    'status' => 'error',
                    'message' => $e->getMessage()
                ];
            }
        }

        // Test Search Console connection
        if ($this->search_console_client) {
            try {
                $test_result = $this->search_console_client->test_connection();
                $results['search_console'] = [
                    'status' => $test_result['success'] ? 'connected' : 'error',
                    'message' => $test_result['message'],
                    'details' => $test_result
                ];
            } catch (\Exception $e) {
                $results['search_console'] = [
                    'status' => 'error',
                    'message' => $e->getMessage()
                ];
            }
        }

        // Test PageSpeed connection
        if ($this->pagespeed_client) {
            try {
                $test_result = $this->pagespeed_client->test_connection();
                $results['pagespeed'] = [
                    'status' => $test_result['success'] ? 'connected' : 'error',
                    'message' => $test_result['message'],
                    'details' => $test_result
                ];
            } catch (\Exception $e) {
                $results['pagespeed'] = [
                    'status' => 'error',
                    'message' => $e->getMessage()
                ];
            }
        }

        return $results;
    }

    /**
     * Get analytics dashboard data
     * Combines data from all Google APIs with caching
     *
     * @param string $date_range Date range for data
     * @return array Dashboard data
     */
    public function get_dashboard_data(string $date_range = '30d'): array {
        $cache_key = "analytics_dashboard_v4_{$date_range}";
        $cached_data = get_transient($cache_key);

        if ($cached_data !== false) {
            return $cached_data;
        }

        $dashboard_data = [
            'traffic' => [],
            'search_performance' => [],
            'core_web_vitals' => [],
            'last_updated' => current_time('mysql'),
            'date_range' => $date_range
        ];

        $retry_count = 0;
        $max_retries = 1;

        while ($retry_count <= $max_retries) {
            try {
                // Get Google Analytics traffic data
                if ($this->analytics_client) {
                    $dashboard_data['traffic'] = $this->analytics_client->get_traffic_data($date_range);
                    $dashboard_data['organic_traffic'] = $this->analytics_client->get_organic_traffic($date_range);
                    $dashboard_data['top_pages'] = $this->analytics_client->get_top_pages(10, $date_range);
                }

                // Ensure clients are initialized (lazy load)
                if (!$this->search_console_client || !$this->search_analytics_client) {
                    $this->initialize_clients();
                }

                // Get Search Console data
                if ($this->search_console_client) {
                    $site_url = $this->get_setting('search_console_property', get_site_url());
                    // Get totals
                    $totals = $this->search_console_client->get_search_totals($site_url, $date_range);

                    // Get performance data (keywords) using new client
                    if ($this->search_analytics_client) {
                        // GSC data has a 2-day delay; use D-2 as end_date to match the GSC dashboard.
                        $days       = (int) str_replace('d', '', $date_range);
                        $end_date   = gmdate('Y-m-d', strtotime('-2 days'));
                        $start_date = gmdate('Y-m-d', strtotime('-' . ($days - 1) . ' days', strtotime($end_date)));

                        $search_performance = $this->search_analytics_client->get_search_analytics_data(
                            $site_url,
                            $start_date,
                            $end_date,
                            ['query'],
                            1000
                        );
                    } else {
                        // Fallback to old client if new one fails init (shouldn't happen if they use same creds)
                        $search_performance = $this->search_console_client->get_search_performance($site_url, $date_range, ['query'], 1000);
                    }

                    // Calculate position distribution
                    $position_distribution = [
                        'top_3' => 0,
                        '4_10' => 0,
                        '10_50' => 0,
                        '51_100' => 0
                    ];

                    foreach ($search_performance['rows'] ?? [] as $row) {
                        $position = $row['position'] ?? 0;
                        if ($position <= 3) {
                            $position_distribution['top_3']++;
                        } elseif ($position <= 10) {
                            $position_distribution['4_10']++;
                        } elseif ($position <= 50) {
                            $position_distribution['10_50']++;
                        } elseif ($position <= 100) {
                            $position_distribution['51_100']++;
                        }
                    }

                    $dashboard_data['search_performance'] = array_merge($search_performance, [
                        'totals' => $totals,
                        'position_distribution' => $position_distribution
                    ]);

                    $dashboard_data['page_performance'] = $this->search_console_client->get_page_performance($site_url, $date_range, 10);
                } // Closing Search Console block

                // Get Core Web Vitals data (isolated try-catch so PageSpeed failures
                // don't abort the entire dashboard data collection)
                if ($this->pagespeed_client) {
                    try {
                        $site_url = get_site_url();
                        $dashboard_data['core_web_vitals'] = $this->pagespeed_client->get_core_web_vitals($site_url);
                    } catch (\Exception $psi_error) {
                        // Log the PageSpeed error but don't fail the whole dashboard
                        $dashboard_data['core_web_vitals'] = [
                            'error' => $psi_error->getMessage(),
                            'note'  => 'PageSpeed data unavailable. This is expected on localhost or non-public URLs.'
                        ];
                    }
                }

                // If successful, break loop
                break;
            } catch (\Exception $e) {
                // Check for 401 error
                if ($e->getCode() === 401 && $retry_count < $max_retries) {
                    $this->refresh_access_token(true); // Force refresh

                    // Re-initialize clients with new token
                    $this->initialize_clients();

                    $retry_count++;
                    continue;
                }
                $dashboard_data['error'] = $e->getMessage();
                break;
            }
        }

        // Add last updated timestamp
        $dashboard_data['last_updated'] = current_time('mysql');

        // Cache the results
        set_transient($cache_key, $dashboard_data, $this->cache_duration);

        return $dashboard_data;
    }

    /**
     * Get SEO opportunities using Search Console data
     *
     * @param string $date_range Date range for analysis
     * @return array SEO opportunities
     */
    public function get_seo_opportunities(string $date_range = '30d'): array {
        $cache_key = "seo_opportunities_{$date_range}";
        $cached_data = get_transient($cache_key);

        if ($cached_data !== false) {
            return $cached_data;
        }

        $opportunities = [
            'keyword_opportunities' => [],
            'page_opportunities' => [],
            'device_insights' => [],
            'last_updated' => current_time('mysql')
        ];

        $retry_count = 0;
        $max_retries = 1;

        while ($retry_count <= $max_retries) {
            try {
                if ($this->search_console_client) {
                    $site_url = $this->get_setting('search_console_property', get_site_url());

                    // Get keyword opportunities
                    $opportunities['keyword_opportunities'] = $this->search_console_client->get_keyword_opportunities($site_url, $date_range);

                    // Get device performance insights
                    $opportunities['device_insights'] = $this->search_console_client->get_device_performance($site_url, $date_range);

                    // Get search appearance data
                    $opportunities['search_appearance'] = $this->search_console_client->get_search_appearance($site_url, $date_range);
                }

                // If successful, break loop
                break;
            } catch (\Exception $e) {
                // Check for 401 error
                if ($e->getCode() === 401 && $retry_count < $max_retries) {
                    $this->refresh_access_token(true); // Force refresh

                    // Re-initialize clients with new token
                    $this->initialize_clients();

                    $retry_count++;
                    continue;
                }

                $opportunities['error'] = $e->getMessage();
                break;
            }
        }

        // Cache the results
        set_transient($cache_key, $opportunities, $this->cache_duration);

        return $opportunities;
    }

    private function get_setting(string $key, $default = '') {
        $integrations_settings = $this->settings_manager->get_settings('integrations');
        $analytics_settings = $this->settings_manager->get_settings('seo_analytics');

        // Merge settings to allow access to both categories
        $all_settings = array_merge($integrations_settings, $analytics_settings);

        return $all_settings[$key] ?? $default;
    }

    /**
     * One-click setup for Google Search Console verification
     * Following ThinkRank setup patterns
     *
     * @param string $site_url Site URL to verify
     * @return array Setup results
     */
    public function setup_search_console_verification(string $site_url): array {
        try {
            if (!$this->search_console_client) {
                return [
                    'success' => false,
                    'message' => 'Search Console API key not configured'
                ];
            }

            $verification_result = $this->search_console_client->verify_site($site_url);

            if ($verification_result['success']) {
                // Update settings with verified site URL
                $this->settings_manager->update_settings(['search_console_property' => $site_url], 'seo_analytics');
            }

            return $verification_result;
        } catch (\Exception $e) {
            return [
                'success' => false,
                'message' => $e->getMessage()
            ];
        }
    }

    /**
     * Get site indexing status
     *
     * @return array Indexing status data
     */
    public function get_indexing_status(): array {
        $cache_key = 'indexing_status';
        $cached_data = get_transient($cache_key);

        if ($cached_data !== false) {
            return $cached_data;
        }

        $indexing_data = [
            'status' => 'unknown',
            'last_updated' => current_time('mysql')
        ];

        try {
            if ($this->search_console_client) {
                $site_url = $this->get_setting('search_console_property', get_site_url());
                $indexing_data = $this->search_console_client->get_indexing_status($site_url);
            }
        } catch (\Exception $e) {
            $indexing_data['error'] = $e->getMessage();
        }

        // Cache for 1 hour
        set_transient($cache_key, $indexing_data, 3600);

        return $indexing_data;
    }

    /**
     * Force refresh of all cached data
     *
     * @return array Refresh results
     */
    public function refresh_data(): array {
        // Clear all analytics-related transients
        $cache_keys = [
            'analytics_dashboard_v4_7d',
            'analytics_dashboard_v4_30d',
            'analytics_dashboard_v4_90d',
            'seo_opportunities_7d',
            'seo_opportunities_30d',
            'seo_opportunities_90d',
            'seo_insights_7d',
            'seo_insights_30d',
            'seo_insights_90d',
            'indexing_status'
        ];

        $cleared = 0;
        foreach ($cache_keys as $key) {
            if (delete_transient($key)) {
                $cleared++;
            }
        }

        return [
            'success' => true,
            'message' => "Cleared {$cleared} cached data entries",
            'cleared_count' => $cleared,
            'timestamp' => current_time('mysql')
        ];
    }

    /**
     * Clear insights cache specifically
     *
     * @return void
     */
    private function clear_insights_cache(): void {
        $insight_cache_keys = [
            'seo_insights_7d',
            'seo_insights_30d',
            'seo_insights_90d'
        ];

        foreach ($insight_cache_keys as $key) {
            delete_transient($key);
        }
    }

    /**
     * Get client status for debugging
     *
     * @return array Client status information
     */
    public function get_client_status(): array {
        return [
            'google_analytics' => [
                'initialized' => !is_null($this->analytics_client),
                'api_key_configured' => !empty($this->get_setting('google_analytics_api_key')),
                'property_id_configured' => !empty($this->get_setting('google_analytics_property_id'))
            ],
            'search_console' => [
                'initialized' => !is_null($this->search_console_client),
                'api_key_configured' => !empty($this->get_setting('google_search_console_api_key')),
                'site_url_configured' => !empty($this->get_setting('search_console_property'))
            ],
            'pagespeed' => [
                'initialized' => !is_null($this->pagespeed_client),
                'api_key_configured' => !empty($this->get_setting('google_pagespeed_api_key'))
            ],
            'cache_duration' => $this->cache_duration,
            'last_checked' => current_time('mysql')
        ];
    }

    /**
     * Cleanup expired cache data
     * Following ThinkRank cache cleanup patterns
     *
     * @return void
     */
    public function cleanup_cache(): void {
        // WordPress handles transient cleanup automatically
        // This method is for future custom cache cleanup if needed
    }

    // ========================================
    // SEO Intelligence Enhancement Methods
    // ========================================

    /**
     * Get intelligent dashboard data with trends and insights
     *
     * @param string $date_range Date range for analysis
     * @return array Enhanced dashboard data with intelligence
     */
    public function get_intelligent_dashboard_data(string $date_range = '30d'): array {
        // Get base dashboard data
        $dashboard_data = $this->get_dashboard_data($date_range);

        // Check if there's an error in the data
        if (isset($dashboard_data['error'])) {
            return [
                'success' => false,
                'data' => null,
                'message' => 'Failed to retrieve dashboard data: ' . $dashboard_data['error'],
                'timestamp' => current_time('mysql')
            ];
        }

        // Check if we have real data available
        if (!$this->has_real_data($dashboard_data)) {
            return [
                'success' => false,
                'data' => null,
                'message' => 'No analytics data available yet. Please ensure your Google Analytics and Search Console are properly configured and have collected data.',
                'timestamp' => current_time('mysql')
            ];
        }

        // Initialize intelligence classes — only available in Pro
        if (
            !class_exists('ThinkRank\SEO\SEO_Trend_Analyzer') ||
            !class_exists('ThinkRank\SEO\SEO_Scoring_Engine') ||
            !class_exists('ThinkRank\SEO\SEO_Insight_Generator')
        ) {
            return [
                'success' => false,
                'data' => null,
                'message' => 'Intelligent dashboard requires ThinkRank Pro.',
                'timestamp' => current_time('mysql')
            ];
        }

        $trend_analyzer = new SEO_Trend_Analyzer();
        $scoring_engine = new SEO_Scoring_Engine();
        $insight_generator = new SEO_Insight_Generator();

        $data = $dashboard_data;

        // Generate trend analysis
        $current_data = $data;
        $historical_data = $this->get_historical_data($date_range);

        $trends = [
            'traffic_trends' => $trend_analyzer->analyze_traffic_trends($current_data, $historical_data),
            'keyword_trends' => $trend_analyzer->analyze_keyword_trends($data['search_performance'] ?? [], $date_range),
            'content_trends' => $trend_analyzer->analyze_content_trends($data, $data['search_performance'] ?? [])
        ];

        // Calculate SEO health score
        $seo_health = $scoring_engine->calculate_seo_health_score($data, $data['search_performance'] ?? []);

        // Generate insights
        $insights = [
            'traffic_insights' => $insight_generator->generate_traffic_insights($trends['traffic_trends']),
            'keyword_insights' => $insight_generator->generate_keyword_insights($trends['keyword_trends']),
            'content_insights' => $insight_generator->generate_content_insights($trends['content_trends'])
        ];

        // Combine all intelligence data
        $enhanced_data = array_merge($data, [
            'intelligence' => [
                'trends' => $trends,
                'seo_health_score' => $seo_health,
                'insights' => $insights,
                'last_analyzed' => current_time('mysql')
            ]
        ]);

        return [
            'success' => true,
            'data' => $enhanced_data,
            'message' => 'Intelligent dashboard data retrieved successfully'
        ];
    }

    /**
     * Get intelligent SEO opportunities with prioritization
     *
     * @param string $date_range Date range for analysis
     * @return array Enhanced opportunities with intelligence
     */
    public function get_intelligent_seo_opportunities(string $date_range = '30d'): array {
        // Get base opportunities data
        $opportunities_data = $this->get_seo_opportunities($date_range);

        // Check if there's an error in the data
        if (isset($opportunities_data['error'])) {
            return [
                'success' => false,
                'data' => null,
                'message' => 'Failed to retrieve opportunities data: ' . $opportunities_data['error'],
                'timestamp' => current_time('mysql')
            ];
        }

        // Check if we have real search console data for opportunities
        $search_performance = $opportunities_data['search_performance'] ?? [];
        $has_search_data = !empty($search_performance['rows']) ||
            ($search_performance['total_clicks'] ?? 0) > 0 ||
            ($search_performance['total_impressions'] ?? 0) > 0;

        if (!$has_search_data) {
            return [
                'success' => false,
                'data' => null,
                'message' => 'No Search Console data available yet. Please ensure your Search Console is properly configured and has collected data.',
                'timestamp' => current_time('mysql')
            ];
        }

        // Initialize intelligence classes — only available in Pro
        if (
            !class_exists('ThinkRank\SEO\SEO_Opportunity_Detector') ||
            !class_exists('ThinkRank\SEO\SEO_Scoring_Engine')
        ) {
            return [
                'success' => false,
                'data' => null,
                'message' => 'Intelligent opportunities require ThinkRank Pro.',
                'timestamp' => current_time('mysql')
            ];
        }

        $opportunity_detector = new SEO_Opportunity_Detector();
        $scoring_engine = new SEO_Scoring_Engine();

        $data = $opportunities_data;

        // Detect intelligent opportunities
        $search_console_data = $data['search_performance'] ?? [];
        $analytics_data = $data;

        $intelligent_opportunities = [
            'quick_wins' => $opportunity_detector->detect_quick_wins($search_console_data, $analytics_data),
            'content_opportunities' => $opportunity_detector->identify_content_opportunities($search_console_data, $analytics_data),
            'keyword_opportunities' => $scoring_engine->score_keyword_opportunities($search_console_data)
        ];

        // Prioritize all opportunities
        $all_opportunities = array_merge(
            $intelligent_opportunities['quick_wins']['opportunities'] ?? [],
            $intelligent_opportunities['content_opportunities']['opportunities'] ?? [],
            $intelligent_opportunities['keyword_opportunities']['opportunities'] ?? []
        );

        $prioritized = $opportunity_detector->prioritize_opportunities($all_opportunities);
        $impact_matrix = $opportunity_detector->calculate_impact_effort_matrix($all_opportunities);

        // Enhance original data with intelligence
        $enhanced_data = array_merge($data, [
            'intelligent_opportunities' => $intelligent_opportunities,
            'prioritized_opportunities' => $prioritized,
            'impact_effort_matrix' => $impact_matrix,
            'opportunity_summary' => $this->generate_opportunity_summary($intelligent_opportunities),
            'last_analyzed' => current_time('mysql')
        ]);

        return [
            'success' => true,
            'data' => $enhanced_data,
            'message' => 'Intelligent SEO opportunities retrieved successfully'
        ];
    }

    /**
     * Get SEO performance insights
     *
     * @param string $date_range Date range for analysis
     * @return array SEO insights data
     */
    public function get_seo_insights(string $date_range = '30d'): array {
        $cache_key = "seo_insights_{$date_range}";
        $cached_data = get_transient($cache_key);

        if ($cached_data !== false) {
            return [
                'success' => true,
                'data' => $cached_data,
                'cached' => true,
                'message' => 'SEO insights retrieved from cache'
            ];
        }

        try {
            // Get dashboard data for analysis
            $dashboard_result = $this->get_intelligent_dashboard_data($date_range);

            if (!$dashboard_result['success']) {
                return $dashboard_result;
            }

            $dashboard_data = $dashboard_result['data'];
            $intelligence = $dashboard_data['intelligence'] ?? [];

            // Initialize insight generator — only available in Pro
            if (!class_exists('ThinkRank\SEO\SEO_Insight_Generator')) {
                return [
                    'success' => false,
                    'data' => null,
                    'message' => 'SEO insights require ThinkRank Pro.',
                    'timestamp' => current_time('mysql')
                ];
            }

            $insight_generator = new SEO_Insight_Generator();

            // Collect all insights
            $all_insights = [];

            if (!empty($intelligence['insights']['traffic_insights']['insights'])) {
                $all_insights = array_merge($all_insights, $intelligence['insights']['traffic_insights']['insights']);
            }

            if (!empty($intelligence['insights']['keyword_insights']['insights'])) {
                $all_insights = array_merge($all_insights, $intelligence['insights']['keyword_insights']['insights']);
            }

            if (!empty($intelligence['insights']['content_insights']['insights'])) {
                $all_insights = array_merge($all_insights, $intelligence['insights']['content_insights']['insights']);
            }

            // Format and prioritize insights
            $formatted_insights = $insight_generator->format_insights_for_display($all_insights);
            $prioritized_insights = $insight_generator->prioritize_insights_by_impact($formatted_insights);

            $insights_data = [
                'insights' => $prioritized_insights['prioritized_insights'],
                'summary' => [
                    'total_insights' => count($formatted_insights),
                    'high_impact_count' => $prioritized_insights['high_impact_count'],
                    'action_required_count' => $prioritized_insights['action_required_count']
                ],
                'seo_health_score' => $intelligence['seo_health_score'] ?? null,
                'generated_at' => current_time('mysql')
            ];

            // Cache the results
            set_transient($cache_key, $insights_data, $this->cache_duration);

            return [
                'success' => true,
                'data' => $insights_data,
                'cached' => false,
                'message' => 'SEO insights generated successfully'
            ];
        } catch (\Exception $e) {
            return [
                'success' => false,
                'error' => 'Failed to generate SEO insights: ' . $e->getMessage(),
                'data' => null
            ];
        }
    }

    /**
     * Check if real analytics data is available
     *
     * @param array $dashboard_data Dashboard data to check
     * @return bool True if real data is available
     */
    private function has_real_data(array $dashboard_data): bool {
        // Check if we have meaningful traffic data
        $traffic = $dashboard_data['traffic'] ?? [];
        $search_performance = $dashboard_data['search_performance'] ?? [];

        $has_traffic = !empty($traffic) && (
            ($traffic['sessions'] ?? 0) > 0 ||
            ($traffic['pageviews'] ?? 0) > 0 ||
            ($traffic['active_users'] ?? 0) > 0
        );

        $has_search_data = !empty($search_performance) && (
            !empty($search_performance['rows']) ||
            ($search_performance['total_clicks'] ?? 0) > 0 ||
            ($search_performance['total_impressions'] ?? 0) > 0
        );

        return $has_traffic || $has_search_data;
    }

    /**
     * Get historical data for trend comparison
     *
     * @param string $current_range Current date range
     * @return array Historical data
     */
    private function get_historical_data(string $current_range): array {
        // Calculate previous period based on current range
        $previous_range = $this->calculate_previous_period($current_range);

        // Try to get actual historical data from previous period
        $historical_data = $this->get_dashboard_data($previous_range);

        // Return the actual historical data (may be empty if no real data available)
        return [
            'sessions' => $historical_data['traffic']['sessions'] ?? 0,
            'pageviews' => $historical_data['traffic']['pageviews'] ?? 0,
            'organic_traffic' => $historical_data['organic_traffic'] ?? ['organic_traffic' => ['sessions' => 0]],
            'bounce_rate' => $historical_data['traffic']['bounce_rate'] ?? 0,
            'avg_session_duration' => $historical_data['traffic']['avg_session_duration'] ?? 0
        ];
    }

    /**
     * Calculate previous period for comparison
     *
     * @param string $current_range Current range
     * @return string Previous period range
     */
    private function calculate_previous_period(string $current_range): string {
        // Simple mapping for now - could be enhanced with actual date calculations
        $period_mapping = [
            '7d' => '14d',
            '30d' => '60d',
            '90d' => '180d'
        ];

        return $period_mapping[$current_range] ?? '60d';
    }

    /**
     * Generate opportunity summary
     *
     * @param array $opportunities All opportunities
     * @return array Opportunity summary
     */
    private function generate_opportunity_summary(array $opportunities): array {
        $quick_wins_count = count($opportunities['quick_wins']['opportunities'] ?? []);
        $content_opportunities_count = count($opportunities['content_opportunities']['opportunities'] ?? []);
        $keyword_opportunities_count = count($opportunities['keyword_opportunities']['opportunities'] ?? []);

        $total_opportunities = $quick_wins_count + $content_opportunities_count + $keyword_opportunities_count;

        $potential_clicks = 0;
        if (!empty($opportunities['quick_wins']['potential_additional_clicks'])) {
            $potential_clicks = $opportunities['quick_wins']['potential_additional_clicks'];
        }

        return [
            'total_opportunities' => $total_opportunities,
            'quick_wins_count' => $quick_wins_count,
            'content_opportunities_count' => $content_opportunities_count,
            'keyword_opportunities_count' => $keyword_opportunities_count,
            'potential_additional_clicks' => $potential_clicks,
            'priority_recommendation' => $quick_wins_count > 0 ?
                'Focus on quick wins first for immediate impact' :
                'Focus on content optimization for long-term growth'
        ];
    }

    /**
     * Clear intelligence cache
     *
     * @return array Clear result
     */
    public function clear_intelligence_cache(): array {
        $intelligence_cache_keys = [
            'seo_insights_7d',
            'seo_insights_30d',
            'seo_insights_90d',
            'intelligent_dashboard_7d',
            'intelligent_dashboard_30d',
            'intelligent_dashboard_90d',
            'intelligent_opportunities_7d',
            'intelligent_opportunities_30d',
            'intelligent_opportunities_90d'
        ];

        $cleared = 0;
        foreach ($intelligence_cache_keys as $key) {
            if (delete_transient($key)) {
                $cleared++;
            }
        }

        return [
            'success' => true,
            'message' => "Cleared {$cleared} intelligence cache entries",
            'cleared_count' => $cleared,
            'timestamp' => current_time('mysql')
        ];
    }
}

```
