/home/othynbht/www/wp-content/plugins/siteseo-pro/main/googleconsole.php
<?php
/*
* SITESEO
* https://siteseo.io
* (c) SiteSEO Team
*/

namespace SiteSEOPro;

if(!defined('ABSPATH')){
	die('Hacking Attempt !');
}

class GoogleConsole{

	static function save_tokens($tokens){
		return update_option('siteseo_google_tokens', $tokens);
	}

	static function get_tokens(){
		return get_option('siteseo_google_tokens', []);
	}

	static function generate_tokens(){

		$auth_code = isset($_GET['siteseo_auth_code']) ? sanitize_text_field(wp_unslash($_GET['siteseo_auth_code'])) : '';

		$post = [
			'code'   => $auth_code,
			'action' => 'generate_tokens'
		];

		$resp = wp_remote_post(SITESEO_API . '/search-console/token.php', [
			'body' => $post,
			'timeout' => 30
		]);

		if(is_wp_error($resp)){
			return ['error' => 'Google Search Console: ' . $resp->get_error_message()];
		}

		$body = wp_remote_retrieve_body($resp);

		if(empty($body)){
			return ['error' => 'Google Search Console: Empty response received from API'];
		}

		$data = json_decode($body, true);

		if(!empty($data['error'])){
			if(is_array($data['error'])){
				return ['error' => 'Google Search Console: '.$data['error']['code'].' : '.$data['error']['message']];
			} else {
				return ['error' => 'Google Search Console: '.$data['error'].' : '.$data['error_description']];
			}
		}

		if(empty($data['access_token'])){
			return ['error' => 'Google Search Console: Access token not found in API response'];
		}

		if(empty($data['refresh_token'])){
			return ['error' => 'Google Search Console: Refresh token not found in API response'];
		}

		$tokens = [
			'access_token'  => $data['access_token'],
			'refresh_token' => $data['refresh_token'],
			'created_time'  => time(),
		];

		self::save_tokens($tokens);

		return $tokens;
	}

	static function generate_access_token(){
		$tokens = self::get_tokens();

		if(empty($tokens['refresh_token'])){
			return ['error' => 'No refresh token available'];
		}

		$post_data = [
			'refresh_token' => $tokens['refresh_token'],
			'action' => 'generate_access_token'
		];

		$resp = wp_remote_post(SITESEO_API. '/search-console/token.php', [
			'body' => $post_data,
			'timeout' => 30
		]);

		if(is_wp_error($resp)){
			return ['error' => $resp->get_error_message()];
		}

		$body = wp_remote_retrieve_body($resp);
		$code = wp_remote_retrieve_response_code($resp);

		if($code < 200 || $code > 399){
			if(!empty($body)){
				$data = json_decode($body, true);
				if(!empty($data)){
					return ['error' => $data];
				}
			}

			return ['error' => __('Google Search Console: Error response received from API with status code ', 'siteseo-pro') . $code];
		}

		if(empty($body)){
			return ['error' => __('Google Search Console: Empty response received from API', 'siteseo-pro')];
		}

		$data = json_decode($body, true);

		if(empty($data)){
			return ['error' => __('Google Search Console: Empty response received from API', 'siteseo-pro')];
		}

		if(isset($data['error'])){
			return ['error' => $data['error']];
		}

		$tokens['access_token'] = $data['access_token'];
		
		self::save_tokens($tokens);
		return $tokens;
	}

	static function is_connected(){
		$data = get_option('siteseo_google_tokens', []);
		return !empty($data['connected']);
	}

	static function update_gsc_connection_status(){
		$data = get_option('siteseo_google_tokens', []);
		if(!is_array($data)){
			$data = [];
		}
		
		$data['connected'] = true;
		update_option('siteseo_google_tokens', $data);
	}

	static function disconnect(){
		delete_option('siteseo_google_tokens');
		delete_option('siteseo_search_console_data');
		return true;
	}

	static function get_page_title_from_url($url){
		$parsed_url = wp_parse_url($url);

		// If SPA URL contains a hash fragment, return the full URL
		if(isset($parsed_url['fragment']) && !empty($parsed_url['fragment'])){
			return $url;
		}

		$domain = isset($parsed_url['host']) ? $parsed_url['host'] : '';
		$path = isset($parsed_url['path']) ? rtrim($parsed_url['path'], '/') : '';

		// Homepage case
		if(empty($path) || $path === '/'){
			return $domain;
		}

		// Internal pages
		return $path . '/';
	}

	static function process_analytics_data($date_data, $pages_data = null, $keyword_data = null, $content_performance_data = null, $device = null, $country = null){

		$processed = [
			'metrics' => [
				'impressions' => ['current' => 0, 'change' => 0, 'trend' => 'neutral'],
				'clicks' => ['current' => 0, 'change' => 0, 'trend' => 'neutral'],
				'ctr' => ['current' => 0, 'change' => 0, 'trend' => 'neutral'],
				'position' => ['current' => 0, 'change' => 0, 'trend' => 'neutral']
			],
			'top_pages' => [],
			'top_keywords' => [],
			'content_ranking' => [],
			'top_loss_pages' => [],
			'top_winning_pages' => [],
			'top_winning_keywords' => [],
			'top_loss_keywords' => [],
			'chart_data' => [
				'impressions' => [],
				'clicks' => [],
				'dates' => [],
				'position' => [],
				'ctr' => [],
			],
			'keyword_data' => [
				'top3' => [],
				'pos4_10' => [],
				'pos11_50' => [],
				'pos50_100' => [],
				'dates' => []
			],
			'keyword_distribution' => [
				'top3' => 0,
				'pos4_10' => 0,
				'pos11_50' => 0,
				'pos50_100' => 0
			],
			'country_audience' =>[],
			'device_audience' => [],
			'total_devices_clicks' => [],
		];

		// Process main date-based data
		if(isset($date_data['rows']) && !empty($date_data['rows'])){
			$processed = self::process_date_data($date_data, $processed);
			
			// Process keyword data
			if($keyword_data && isset($keyword_data['rows']) && !empty($keyword_data['rows'])){
				$processed = self::process_keyword_data($keyword_data, $processed);
			}

			// Process pages data
			if($pages_data && isset($pages_data['rows']) && !empty($pages_data['rows'])){
				$processed = self::process_pages_data($pages_data, $processed);
			}

			// Process content performance data
			if($content_performance_data && isset($content_performance_data['rows']) && !empty($content_performance_data['rows'])){
				$processed = self::process_content_performance_data($content_performance_data, $processed);
			}
			
			if($device && isset($device['rows']) && !empty($device['rows'])){
				$processed = self::process_device_data($device, $processed);
			}
			
			if($country && isset($country['rows']) && !empty($country['rows'])){
				$processed = self::process_country_data($country, $processed);
			}
		}

		// Save
		return update_option('siteseo_search_console_data', $processed);
	}
	
	static function process_device_data($device, $processed){
		$device_data = [];
		$total_clicks = 0;

		if(isset($device['rows']) && !empty($device['rows'])){
			foreach($device['rows'] as $row){
				$device_name = $row['keys'];
				$impressions = $row['impressions'];
				$clicks = $row['clicks'];
				$ctr = $row['ctr'];
				$position = $row['position'];

				// Add to total_clicks
				$total_clicks += $clicks;

				$device_data[] = [
					'device' => $device_name,
					'impressions' => round($impressions),
					'clicks' => $clicks,
					'ctr' => round($ctr),
					'position' => $position
				];
			}

			// Sort by impressions (descending)
			usort($device_data, ['self', 'sort_by_impressions_desc']);

			// Store
			$processed['device_audience'] = $device_data;
			$processed['total_devices_clicks'] = $total_clicks; // Added total_clicks
		}

		return $processed;
	}

	static function process_country_data($country, $processed){
		$country_data = [];

		if(isset($country['rows']) && !empty($country['rows'])){
			foreach($country['rows'] as $row){
				$country_code = $row['keys'][0];
				$impressions = $row['impressions'];
				$clicks = $row['clicks'];
				$ctr = $row['ctr'];
				$position = $row['position'];

				// Convert country code to full country name
				$country_name = self::get_country_name($country_code);

				$country_data[] = [
					'country' => $country_name,
					'impressions' => round($impressions),
					'clicks' => $clicks,
					'ctr' => round($ctr),
					'position' => $position
				];
			}

			// Sort by impressions (descending)
			usort($country_data, ['self', 'sort_by_impressions_desc']);

			// Store
			$processed['country_audience'] = $country_data;
		}
		
		return $processed;
	}
	
	static function get_country_name($country_code){
		// First, let's normalize the country code
		$normalized_code = strtoupper(trim($country_code));
		
		$country_names = [
			// Standard ISO codes
			'US' => __('United States', 'siteseo-pro'), 
			'GB' => __('United Kingdom', 'siteseo-pro'), 
			'UK' => __('United Kingdom', 'siteseo-pro'), 
			'CA' => __('Canada', 'siteseo-pro'), 
			'AU' => __('Australia', 'siteseo-pro'), 
			'DE' => __('Germany', 'siteseo-pro'), 
			'FR' => __('France', 'siteseo-pro'), 
			'IT' => __('Italy', 'siteseo-pro'), 
			'ES' => __('Spain', 'siteseo-pro'), 
			'JP' => __('Japan', 'siteseo-pro'), 
			'IN' => __('India', 'siteseo-pro'), 
			'BR' => __('Brazil', 'siteseo-pro'), 
			'MX' => __('Mexico', 'siteseo-pro'), 
			'NL' => __('Netherlands', 'siteseo-pro'), 
			'SE' => __('Sweden', 'siteseo-pro'), 
			'NO' => __('Norway', 'siteseo-pro'), 
			'DK' => __('Denmark', 'siteseo-pro'),
			'FI' => __('Finland', 'siteseo-pro'), 
			'RU' => __('Russia', 'siteseo-pro'), 
			'CN' => __('China', 'siteseo-pro'), 
			'KR' => __('South Korea', 'siteseo-pro'),
			'USA' => __('United States', 'siteseo-pro'), 
			'UNITED STATES' => __('United States', 'siteseo-pro'),
			'UNITED KINGDOM' => __('United Kingdom', 'siteseo-pro'),
			'AUS' => __('Australia', 'siteseo-pro'), 
			'AUSTRALIA' => __('Australia', 'siteseo-pro'),
			'GER' => __('Germany', 'siteseo-pro'),
			'GERMANY' => __('Germany', 'siteseo-pro'),
			'FRA' => __('France', 'siteseo-pro'), 
			'FRANCE' => __('France', 'siteseo-pro'),
			'ITA' => __('Italy', 'siteseo-pro'), 
			'ITALY' => __('Italy', 'siteseo-pro'),
			'SPA' => __('Spain', 'siteseo-pro'), 
			'SPAIN' => __('Spain', 'siteseo-pro'),
			'JPN' => __('Japan', 'siteseo-pro'), 
			'JAPAN' => __('Japan', 'siteseo-pro'),
			'IND' => __('India', 'siteseo-pro'), 
			'INDIA' => __('India', 'siteseo-pro'),
			'BRA' => __('Brazil', 'siteseo-pro'), 
			'BRAZIL' => __('Brazil', 'siteseo-pro'),
			'MEX' => __('Mexico', 'siteseo-pro'), 
			'MEXICO' => __('Mexico', 'siteseo-pro'),
			'NLD' => __('Netherlands', 'siteseo-pro'), 
			'NETHERLANDS' => __('Netherlands', 'siteseo-pro'),
			'SWE' => __('Sweden', 'siteseo-pro'),
			'SWEDEN' => __('Sweden', 'siteseo-pro'),
			'NOR' => __('Norway', 'siteseo-pro'), 
			'NORWAY' => __('Norway', 'siteseo-pro'),
			'DNK' => __('Denmark', 'siteseo-pro'), 
			'DENMARK' => __('Denmark', 'siteseo-pro'),
			'FIN' => __('Finland', 'siteseo-pro'), 
			'FINLAND' => __('Finland', 'siteseo-pro'),
			'RUS' => __('Russia', 'siteseo-pro'),
			'RUSSIA' => __('Russia', 'siteseo-pro'),
			'CHN' => __('China', 'siteseo-pro'), 
			'CHINA' => __('China', 'siteseo-pro'),
			'KOR' => __('South Korea', 'siteseo-pro'),
			'SOUTH KOREA' => __('South Korea', 'siteseo-pro'),
			'AF' => __('Afghanistan', 'siteseo-pro'),
			'AL' => __('Albania', 'siteseo-pro'), 
			'DZ' => __('Algeria', 'siteseo-pro'),
			'AR' => __('Argentina', 'siteseo-pro'),
			'AT' => __('Austria', 'siteseo-pro'), 
			'BH' => __('Bahrain', 'siteseo-pro'),
			'BD' => __('Bangladesh', 'siteseo-pro'),
			'BE' => __('Belgium', 'siteseo-pro'), 
			'BG' => __('Bulgaria', 'siteseo-pro'),
			'CL' => __('Chile', 'siteseo-pro'), 
			'CO' => __('Colombia', 'siteseo-pro'), 
			'CR' => __('Costa Rica', 'siteseo-pro'),
			'HR' => __('Croatia', 'siteseo-pro'),
			'CZ' => __('Czech Republic', 'siteseo-pro'),
			'EG' => __('Egypt', 'siteseo-pro'),
			'GR' => __('Greece', 'siteseo-pro'), 
			'HK' => __('Hong Kong', 'siteseo-pro'),
			'HU' => __('Hungary', 'siteseo-pro'),
			'ID' => __('Indonesia', 'siteseo-pro'),
			'IE' => __('Ireland', 'siteseo-pro'),
			'IL' => __('Israel', 'siteseo-pro'),
			'JO' => __('Jordan', 'siteseo-pro'),
			'KW' => __('Kuwait', 'siteseo-pro'), 
			'LB' => __('Lebanon', 'siteseo-pro'),
			'MY' => __('Malaysia', 'siteseo-pro'),
			'MA' => __('Morocco', 'siteseo-pro'), 
			'NZ' => __('New Zealand', 'siteseo-pro'),
			'NG' => __('Nigeria', 'siteseo-pro'), 
			'OM' => __('Oman', 'siteseo-pro'), 
			'PK' => __('Pakistan', 'siteseo-pro'),
			'PE' => __('Peru', 'siteseo-pro'),
			'PH' => __('Philippines', 'siteseo-pro'),
			'PL' => __('Poland', 'siteseo-pro'),
			'PT' => __('Portugal', 'siteseo-pro'),
			'QA' => __('Qatar', 'siteseo-pro'),
			'RO' => __('Romania', 'siteseo-pro'),
			'SA' => __('Saudi Arabia', 'siteseo-pro'), 
			'SG' => __('Singapore', 'siteseo-pro'),
			'ZA' => __('South Africa', 'siteseo-pro'),
			'LK' => __('Sri Lanka', 'siteseo-pro'),
			'CH' => __('Switzerland', 'siteseo-pro'),
			'TW' => __('Taiwan', 'siteseo-pro'),
			'TH' => __('Thailand', 'siteseo-pro'),
			'TR' => __('Turkey', 'siteseo-pro'),
			'UA' => __('Ukraine', 'siteseo-pro'),
			'AE' => __('United Arab Emirates', 'siteseo-pro'), 
			'VE' => __('Venezuela', 'siteseo-pro'),
			'VN' => __('Vietnam', 'siteseo-pro'),		
		];
		
		// match
		if(isset($country_names[$normalized_code])){
			return $country_names[$normalized_code];
		}
		
		// If no match found ,return the original code
		foreach($country_names as $code => $name){
			if(strpos($normalized_code, $code) !== false || strpos($name, $normalized_code) !== false){
				return $name;
			}
		}
		
		if(strlen($normalized_code) == 2){
			return $normalized_code . ' (Unknown)';
		}
		
		return $country_code;
	}

	static function process_date_data($date_data, $processed){
		$daily_data = [];
		$monthly_data = [];
		$total_impressions = 0;
		$total_clicks = 0;
		$total_position = 0;
		$total_days = 0;

		foreach($date_data['rows'] as $row){
			if(!isset($row['keys'][0])) continue;
			
			$date = $row['keys'][0];
			$day = gmdate('Y-m-d', strtotime($date));
			$month = gmdate('Y-m', strtotime($date));

			// Initialize daily data
			if(!isset($daily_data[$day])){
				$daily_data[$day] = [
					'impressions' => 0,
					'clicks' => 0,
					'position_total' => 0,
					'rows_count' => 0
				];
			}

			// Initialize monthly data
			if(!isset($monthly_data[$month])){
				$monthly_data[$month] = [
					'impressions' => 0,
					'clicks' => 0,
					'position_total' => 0,
					'rows_count' => 0
				];
			}

			$impressions = isset($row['impressions']) ? $row['impressions'] : 0;
			$clicks = isset($row['clicks']) ? $row['clicks'] : 0;
			$position = isset($row['position']) ? $row['position'] : 0;

			// Update daily data
			$daily_data[$day]['impressions'] += $impressions;
			$daily_data[$day]['clicks'] += $clicks;
			$daily_data[$day]['position_total'] += $position;
			$daily_data[$day]['rows_count']++;

			// Update monthly data
			$monthly_data[$month]['impressions'] += $impressions;
			$monthly_data[$month]['clicks'] += $clicks;
			$monthly_data[$month]['position_total'] += $position;
			$monthly_data[$month]['rows_count']++;
		}

		// Prepare chart data
		ksort($daily_data);
		foreach($daily_data as $day => $d){
			$total_impressions += $d['impressions'];
			$total_clicks += $d['clicks'];

			$day_position = ($d['rows_count'] > 0) ? $d['position_total'] / $d['rows_count'] : 0;
			$total_position += $day_position;
			$total_days++;

			$avg_position = ($d['rows_count'] > 0) ? round($d['position_total'] / $d['rows_count'], 2) : 0;
			$ctr = $d['impressions'] > 0 ? round(($d['clicks'] / $d['impressions']) * 100, 2) : 0;

			$processed['chart_data']['dates'][] = $day;
			$processed['chart_data']['impressions'][] = $d['impressions'];
			$processed['chart_data']['clicks'][] = $d['clicks'];
			$processed['chart_data']['ctr'][] = $ctr;
			$processed['chart_data']['position'][] = $avg_position;
		}

		// Calculate metrics
		$processed['metrics']['impressions']['current'] = number_format($total_impressions);
		$processed['metrics']['clicks']['current'] = number_format($total_clicks);
		$processed['metrics']['ctr']['current'] = $total_impressions > 0 ? round(($total_clicks / $total_impressions) * 100, 2) . '%' : '0%';
		$processed['metrics']['position']['current'] = $total_days > 0 ? round($total_position / $total_days, 1) : 0;

		// Calculate trends using monthly data
		$current_month = gmdate('Y-m');
		$previous_month = gmdate('Y-m', strtotime('-1 month'));
		$two_months_ago = gmdate('Y-m', strtotime('-2 month'));

		if (empty($monthly_data[$current_month]['impressions']) && isset($monthly_data[$previous_month])) {
			// If current month has no data, use previous month as "current"
			$current = $monthly_data[$previous_month];
			$previous = isset($monthly_data[$two_months_ago]) ? $monthly_data[$two_months_ago] : ['impressions' => 0, 'clicks' => 0,'position_total' => 0, 'rows_count' => 1];
		} elseif(isset($monthly_data[$current_month]) && isset($monthly_data[$previous_month])) {
			$current = $monthly_data[$current_month];
			$previous = $monthly_data[$previous_month];
		} else {
			$current = ['impressions' => 0, 'clicks' => 0, 'position_total' => 0, 'rows_count' => 1];
			$previous = ['impressions' => 0, 'clicks' => 0, 'position_total' => 0, 'rows_count' => 1];
		}

		// Impression trend
		$current_impressions = $current['impressions'];
		$previous_impressions = $previous['impressions'];
		$impression_change = $current_impressions - $previous_impressions;
		$processed['metrics']['impressions']['change'] = number_format(abs($impression_change));
		$processed['metrics']['impressions']['trend'] = $impression_change >= 0 ? 'positive' : 'negative';

		// Click trend
		$current_clicks = $current['clicks'];
		$previous_clicks = $previous['clicks'];
		$click_change = $current_clicks - $previous_clicks;
		$processed['metrics']['clicks']['change'] = number_format(abs($click_change));
		$processed['metrics']['clicks']['trend'] = $click_change >= 0 ? 'positive' : 'negative';

		// Position trend (lower is better)
		$current_position = ($current['rows_count'] > 0) ? $current['position_total'] / $current['rows_count'] : 0;
		$previous_position = ($previous['rows_count'] > 0) ? $previous['position_total'] / $previous['rows_count'] : 0;
		$position_change = $current_position - $previous_position;
		$processed['metrics']['position']['change'] = round(abs($position_change), 1);
		$processed['metrics']['position']['trend'] = $position_change <= 0 ? 'positive' : 'negative';

		// CTR trend
		$current_ctr = $current_impressions > 0 ? ($current_clicks / $current_impressions) * 100 : 0;
		$previous_ctr = $previous_impressions > 0 ? ($previous_clicks / $previous_impressions) * 100 : 0;
		$ctr_change = $current_ctr - $previous_ctr;
		$processed['metrics']['ctr']['change'] = round(abs($ctr_change), 2) . '%';
		$processed['metrics']['ctr']['trend'] = $ctr_change >= 0 ? 'positive' : 'negative';

		return $processed;
	}

	static function process_keyword_data($keyword_data, $processed){
		$top_keywords = [];
		$top_winning_keywords = [];
		$top_loss_keywords = [];

		$total_keywords_count = 0;
		$keyword_daily_data = [];
		$unique_keywords = [];
		$keyword_aggregated = [];
		
		// Calculate average position for all keywords
		$total_position = 0;
		$keyword_count = 0;
		
		foreach($keyword_data['rows'] as $row){
			if(!isset($row['keys'][0])) continue;
			
			$keyword = $row['keys'][0];
			$date = isset($row['keys'][1]) ? $row['keys'][1] : null;
			
			// Skip if no date
			if(!$date){
				continue;
			}
			
			$day = gmdate('Y-m-d', strtotime($date));
			$impressions = isset($row['impressions']) ? $row['impressions'] : 0;
			$clicks = isset($row['clicks']) ? $row['clicks'] : 0;
			$position = isset($row['position']) ? round($row['position'], 1) : 0;
			$ctr = $impressions > 0 ? round(($clicks / $impressions) * 100, 2) : 0;
			
			// Calculate average position
			if($position > 0){
				$total_position += $position;
				$keyword_count++;
			}
			
			// Track unique keywords with impressions
			if($impressions > 0){
				$unique_keywords[$keyword] = true;
			}

			// Initialize daily data if not exists
			if(!isset($keyword_daily_data[$day])){
				$keyword_daily_data[$day] = [
					'top3' => 0,
					'pos4_10' => 0,
					'pos11_50' => 0,
					'pos50_100' => 0,
					'keywords_tracked' => []
				];
			}

			// Only count each keyword once per day
			if(!in_array($keyword, $keyword_daily_data[$day]['keywords_tracked'])){
				if($impressions > 0 && $position > 0){
					// Categorize by position for daily distribution
					if($position <= 3){
						$keyword_daily_data[$day]['top3']++;
					} elseif($position <= 10){
						$keyword_daily_data[$day]['pos4_10']++;
					} elseif($position <= 50){
						$keyword_daily_data[$day]['pos11_50']++;
					} else{
						$keyword_daily_data[$day]['pos50_100']++;
					}
					
					$keyword_daily_data[$day]['keywords_tracked'][] = $keyword;
				}
			}

			if(!isset($keyword_aggregated[$keyword])){
				$keyword_aggregated[$keyword] = [
					'impressions' => 0,
					'clicks' => 0,
					'position' => 0,
					'position_count' => 0
				];
			}

			$keyword_aggregated[$keyword]['impressions'] += $impressions;
			$keyword_aggregated[$keyword]['clicks'] += $clicks;
			if($position > 0){
				$keyword_aggregated[$keyword]['position'] += $position;
				$keyword_aggregated[$keyword]['position_count']++;
			}
		}
		
		// Count total unique keywords
		$total_keywords_count = count($unique_keywords);

		// Store
		$processed['metrics']['total_keywords'] = [
			'current' => number_format($total_keywords_count),
			'change' => '+0',
			'trend' => 'neutral'
		];

		// Initialize keyword_data 
		$processed['keyword_data']['dates'] = [];
		$processed['keyword_data']['top3'] = [];
		$processed['keyword_data']['pos4_10'] = [];
		$processed['keyword_data']['pos11_50'] = [];
		$processed['keyword_data']['pos50_100'] = [];

		foreach($processed['chart_data']['dates'] as $chart_date){
			$day_label = gmdate('M j', strtotime($chart_date));
			$processed['keyword_data']['dates'][] = $day_label;
			
			if(isset($keyword_daily_data[$chart_date])){
				$data = $keyword_daily_data[$chart_date];
				
				// Use actual counts
				$processed['keyword_data']['top3'][] = $data['top3'];
				$processed['keyword_data']['pos4_10'][] = $data['pos4_10'];
				$processed['keyword_data']['pos11_50'][] = $data['pos11_50'];
				$processed['keyword_data']['pos50_100'][] = $data['pos50_100'];
			} else{
				// No data for this date
				$processed['keyword_data']['top3'][] = 0;
				$processed['keyword_data']['pos4_10'][] = 0;
				$processed['keyword_data']['pos11_50'][] = 0;
				$processed['keyword_data']['pos50_100'][] = 0;
			}
		}
		
		// Calculate overall distribution based on aggregated keywords
		$position_distribution = [
			'top3' => 0,
			'pos4_10' => 0,
			'pos11_50' => 0,
			'pos50_100' => 0
		];
		
		foreach($keyword_aggregated as $keyword => $data){
			if($data['impressions'] == 0) continue;
			
			// Calculate average position for this keyword
			$avg_position = $data['position_count'] > 0 ? $data['position'] / $data['position_count'] : 0;
			
			// Calculate CTR
			$ctr = $data['impressions'] > 0 ? round(($data['clicks'] / $data['impressions']) * 100, 2) : 0;
			
			// Categorize by position
			if($avg_position > 0 && $avg_position <= 3){
				$position_distribution['top3']++;
			} elseif($avg_position > 3 && $avg_position <= 10){
				$position_distribution['pos4_10']++;
			} elseif($avg_position > 10 && $avg_position <= 50){
				$position_distribution['pos11_50']++;
			} elseif($avg_position > 50){
				$position_distribution['pos50_100']++;
			}
			
			// Calculate points for ranking
			$points = self::calculate_keyword_points($data['clicks'], $data['impressions'], $avg_position, $ctr);
			$trend = $points > 50 ? 'winning' : 'loss';
			
			$keyword_data_item = [
				'keyword' => $keyword,
				'clicks' => number_format($data['clicks']),
				'impressions' => number_format($data['impressions']),
				'position' => round($avg_position, 1),
				'ctr' => $ctr . '%',
				'points' => $points,
				'trend' => $trend
			];
			
			$top_keywords[] = $keyword_data_item;
			
			// Categorize for winning/loss based on trend
			if($trend === 'winning'){
				$top_winning_keywords[] = $keyword_data_item;
			} else{
				$top_loss_keywords[] = $keyword_data_item;
			}
		}

		// Calculate distribution percentages for the bar chart
		$total_keywords_dist = array_sum($position_distribution);
		if($total_keywords_dist > 0){
			$processed['keyword_distribution']['top3'] = round(($position_distribution['top3'] / $total_keywords_dist) * 100, 1);
			$processed['keyword_distribution']['pos4_10'] = round(($position_distribution['pos4_10'] / $total_keywords_dist) * 100, 1);
			$processed['keyword_distribution']['pos11_50'] = round(($position_distribution['pos11_50'] / $total_keywords_dist) * 100, 1);
			$processed['keyword_distribution']['pos50_100'] = round(($position_distribution['pos50_100'] / $total_keywords_dist) * 100, 1);
		} else{
			$processed['keyword_distribution']['top3'] = 0;
			$processed['keyword_distribution']['pos4_10'] = 0;
			$processed['keyword_distribution']['pos11_50'] = 0;
			$processed['keyword_distribution']['pos50_100'] = 0;
		}
		
		// Sort
		usort($top_keywords, ['self', 'sort_by_impressions_desc']);
		
		usort($top_winning_keywords, ['self', 'sort_by_points_desc']);
		
		usort($top_loss_keywords, ['self', 'sort_by_points_asc']);
		
		$processed['top_keywords'] = array_slice($top_keywords, 0, 10);
		$processed['top_winning_keywords'] = array_slice($top_winning_keywords, 0, 10);
		$processed['top_loss_keywords'] = array_slice($top_loss_keywords, 0, 10);
		
		return $processed;
	}

	static function process_pages_data($pages_data, $processed){
		$top_pages = [];
		$top_loss_pages = [];
		$top_winning_pages = [];

		foreach($pages_data['rows'] as $row){
			if(!isset($row['keys'][0])) continue;
			
			$page_url = $row['keys'][0];
			$impressions = isset($row['impressions']) ? $row['impressions'] : 0;
			$clicks = isset($row['clicks']) ? $row['clicks'] : 0;
			$position = isset($row['position']) ? round($row['position'], 1) : 0;
			$ctr = $impressions > 0 ? round(($clicks / $impressions) * 100, 2) : 0;
			
			// Get clean page title
			$page_title = self::get_page_title_from_url($page_url);
			
			// Calculate actual score based on performance
			$truseo_score = self::calculate_page_score($clicks, $impressions, $position, $ctr);
			
			// Calculate actual difference based on performance metrics
			$diff_value = self::calculate_performance_diff($clicks, $impressions, $position);
			$diff_display = $diff_value > 0 ? '+'.$diff_value : $diff_value;
			
			$page_data = [
				'title' => $page_title,
				'url' => $page_url,
				'clicks' => number_format($clicks),
				'impressions' => number_format($impressions),
				'position' => $position,
				'ctr' => $ctr . '%',
				'indexed' => true,
				'truseo_score' => round($truseo_score),
				'diff' => $diff_display,
				'diff_value' => $diff_value
			];
			
			$top_pages[] = $page_data;
			
			// Categorize for top loss/winning
			if($diff_value < 0){
				$top_loss_pages[] = $page_data;
			} elseif($diff_value > 0){
				$top_winning_pages[] = $page_data;
			}
		}

		// Sort
		usort($top_pages, ['self', 'sort_by_clicks_desc']);

		usort($top_loss_pages, ['self', 'sort_by_diff_asc']);

		usort($top_winning_pages, ['self', 'sort_by_diff_desc']);

		$processed['top_pages'] = $top_pages;
		$processed['top_loss_pages'] = $top_loss_pages;
		$processed['top_winning_pages'] = $top_winning_pages;

		return $processed;
	}

	static function process_content_performance_data($content_performance_data, $processed){
		$content_ranking = [];
		$unique_titles = [];

		foreach($content_performance_data['rows'] as $row){
			if(!isset($row['keys'][0])) continue;
		
			$page_url = $row['keys'][0];
			$last_update = isset($row['keys'][1]) ? $row['keys'][1] : '';
			$impressions = isset($row['impressions']) ? $row['impressions'] : 0;
			$clicks = isset($row['clicks']) ? $row['clicks'] : 0;
			$position = isset($row['position']) ? round($row['position'], 1) : 0;

			// page title
			$page_title = self::get_page_title_from_url($page_url);

			$title_lower = strtolower(trim($page_title));
			if(in_array($title_lower, $unique_titles)){
				continue;
			}
		
			// Add to uniques
			$unique_titles[] = $title_lower;
		
			// Calculate actual performance metrics
			$performance_data = self::calculate_content_performance($clicks, $impressions, $position);
		
			$content_data = [
				'title' => $page_title,
				'url' => $page_url,
				'indexed' => $performance_data['indexed'],
				'last_update' => $last_update,
				'loss' => number_format($performance_data['loss']),
				'drop_percent' => $performance_data['drop_percent'] . '%',
				'performance_score' => $performance_data['performance_score'] . '/100',
				'clicks' => number_format($clicks),
				'impressions' => number_format($impressions),
				'position' => $position
			];

			$content_ranking[] = $content_data;
		}
	
		// Sort
		usort($content_ranking, ['self', 'sort_by_performance_score_desc']);

		$content_ranking = array_slice($content_ranking, 0, 30); // LIMIT to 30 Content Analysis
		$processed['content_ranking'] = $content_ranking;
		return $processed;
	}

	static function sort_by_performance_score_desc($a, $b){
		if($a['performance_score'] == $b['performance_score']) return 0;
		return ($a['performance_score'] < $b['performance_score']) ? 1 : -1;
	}

	// Sort by impressions DESC
	static function sort_by_impressions_desc($a, $b){
		if($a['impressions'] == $b['impressions']) return 0;
		return ($a['impressions'] < $b['impressions']) ? 1 : -1;
	}
	
	static function sort_by_points_desc($a, $b){
		if($a['points'] == $b['points']) return 0;
		return ($a['points'] < $b['points']) ? 1 : -1;
	}

	static function sort_by_points_asc($a, $b){
		if($a['points'] == $b['points']) return 0;
		return ($a['points'] > $b['points']) ? 1 : -1;
	}

	// Sort by clicks DESC
	static function sort_by_clicks_desc($a, $b){
		if($a['clicks'] == $b['clicks']) return 0;
		return ($a['clicks'] < $b['clicks']) ? 1 : -1;
	}
	
	static function sort_by_diff_asc($a, $b){
		return $a['diff_value'] - $b['diff_value'];
	}

	static function sort_by_diff_desc($a, $b){
		return $b['diff_value'] - $a['diff_value'];
	}

	// Sort by position ASC (lower is better)
	static function sort_by_position_asc($a, $b){
		if($a['position'] == $b['position']) return 0;
		return ($a['position'] > $b['position']) ? 1 : -1;
	}

	static function calculate_keyword_points($clicks, $impressions, $position, $ctr){
		$points = 0;

		if($position > 0 && $position <= 3){
			$points += 40;
		} elseif ($position > 3 && $position <= 10){
			$points += 30;
		} elseif ($position > 10 && $position <= 50){
			$points += 15;
		} else {
			$points += 5;
		}
		
		// CTR factor
		if($ctr > 10){
			$points += 30;
		} elseif($ctr > 5){
			$points += 20;
		} elseif($ctr > 2){
			$points += 10;
		} else{
			$points += 5;
		}
		
		// Click factor
		if($clicks > 1000){
			$points += 30;
		} elseif($clicks > 100){
			$points += 20;
		} elseif($clicks > 10){
			$points += 10;
		}
		
		return min(100, $points);
	}

	static function calculate_page_score($clicks, $impressions, $position, $ctr){
		$score = 0;
		
		// Position score
		if($position > 0 && $position <= 3){
			$score += 40;
		} elseif($position > 3 && $position <= 10){
			$score += 30;
		} elseif($position > 10 && $position <= 50){
			$score += 20;
		} else{
			$score += 10;
		}
		
		// CTR score
		$score += min(30, $ctr * 3);
		
		// Click volume score
		if($clicks > 1000) $score += 30;
		elseif($clicks > 100) $score += 20;
		elseif($clicks > 10) $score += 10;
		
		return min(100, $score);
	}

	static function calculate_performance_diff($clicks, $impressions, $position){
		$score = ($clicks * 0.4) + (($impressions / 100) * 0.3) - (($position) * 0.2);
		return max(-10, min(10, round($score)));
	}

	static function calculate_content_performance($clicks, $impressions, $position){
		$ctr = $impressions > 0 ? ($clicks / $impressions) * 100 : 0;
		
		// Calculate performance score
		$performance_score = self::calculate_page_score($clicks, $impressions, $position, $ctr);
		
		return [
			'indexed' => $impressions > 0 ? 'Yes' : 'No',// If impressions > 0, consider indexed
			'loss' => max(0, $impressions - $clicks),// Real loss = impressions - clicks
			'drop_percent' => $ctr > 0 ? round((100 - $ctr), 1) : 0,// Drop % based on CTR
			'performance_score' => round($performance_score)
		];
	}


	static function get_site_url(){
		$saved_site_url = get_option('siteseo_google_tokens');
		return !empty($saved_site_url['site_url']) ? $saved_site_url['site_url'] : '';
	}
	
	static function get_all_analytics(){
		$end_date = gmdate('Y-m-d');
		$start_date = gmdate('Y-m-d', strtotime('-3 months'));
		$site_url = self::get_site_url();
		$base_endpoint = '/webmasters/v3/sites/' . urlencode($site_url) . '/searchAnalytics/query';

		$requests = [
			'date_data' => [
				'endpoint' => $base_endpoint,
				'params' => [
					'startDate' => $start_date,
					'endDate' => $end_date,
					'dimensions' => ['date'],
					'rowLimit' => 1000,
				]
			],
			'pages_data' => [
				'endpoint' => $base_endpoint,
				'params' => [
					'startDate' => $start_date,
					'endDate' => $end_date,
					'dimensions' => ['page'],
					'rowLimit' => 1000,
				]
			],
			'keyword_data' => [
				'endpoint' => $base_endpoint,
				'params' => [
					'startDate' => $start_date,
					'endDate' => $end_date,
					'dimensions' => ['query', 'date'],
					'rowLimit' => 1000,
				]
			],
			'content_performance_data' => [
				'endpoint' => $base_endpoint,
				'params' => [
					'startDate' => $start_date,
					'endDate' => $end_date,
					'dimensions' => ['page', 'date'],
					'rowLimit' => 1000,
				]
			],
			'device_audience' => [
				'endpoint' => $base_endpoint,
				'params' => [
					'startDate' => $start_date,
					'endDate' => $end_date,
					'dimensions' => ['device'],
					'rowLimit' => 1000,
				]
			],
			'country_audience' => [
				'endpoint' => $base_endpoint,
				'params' => [
					'startDate' => $start_date,
					'endDate' => $end_date,
					'dimensions' => ['country'],
					'rowLimit' => 5,
				]
			]
		];

		$results = self::api_batch_request($requests);
		
		if(isset($results['error'])){
			return new \WP_Error('batch_error', $results['error']);
		}

		$date_data = isset($results['date_data']) ? $results['date_data'] : [];
		$pages_data = isset($results['pages_data']) ? $results['pages_data'] : [];
		$keyword_data = isset($results['keyword_data']) ? $results['keyword_data'] : [];
		$content_performance_data = isset($results['content_performance_data']) ? $results['content_performance_data'] : [];
		$device_audience = isset($results['device_audience']) ? $results['device_audience'] : [];
		$country_audience = isset($results['country_audience']) ? $results['country_audience'] : [];
		
		if(isset($date_data['error']) && isset($date_data['error']['message'])){
			return new \WP_Error('date_error', $date_data['error']['message']);
		}

		$processed_data = self::process_analytics_data($date_data, $pages_data, $keyword_data, $content_performance_data, $device_audience, $country_audience);

		return $processed_data;
	}
	
	static function api_batch_request($requests){
		$tokens = self::get_tokens();

		// Check token validity
		if(empty($tokens['created_time']) || (time() - intval($tokens['created_time'])) >= 3600){
			$tokens = self::generate_access_token();
			if(isset($tokens['error'])){
				return $tokens;
			}
		}

		$boundary = 'batch_' . wp_generate_password(20, false);
		$body = '';

		foreach($requests as $key => $req){
			$body .= "--" . $boundary . "\r\n";
			$body .= "Content-Type: application/http\r\n";
			$body .= "Content-ID: " . $key . "\r\n\r\n";
			$body .= "POST /" . $req['endpoint'] . " HTTP/1.1\r\n";
			$body .= "Content-Type: application/json\r\n";
			$body .= "Accept: application/json\r\n\r\n";
			$body .= json_encode($req['params']) . "\r\n";
		}
		$body .= "--" . $boundary . "--";

		$response = wp_remote_post('https://www.googleapis.com/batch/webmasters/v3', [
			'headers' => [
				'Authorization' => 'Bearer ' . $tokens['access_token'],
				'Content-Type' => 'multipart/mixed; boundary=' . $boundary
			],
			'body' => $body,
			'timeout' => 30
		]);

		if(is_wp_error($response)){
			return ['error' => $response->get_error_message()];
		}

		$status_code = wp_remote_retrieve_response_code($response);
		if($status_code < 200 || $status_code > 399){
			return ['error' => __('The batch request responded with status code ', 'siteseo-pro') . $status_code];
		}

		$response_body = wp_remote_retrieve_body($response);
		// Parse multipart response
		$results = [];
		
		// Extract the boundary from the response Content-Type header
		$content_type = wp_remote_retrieve_header($response, 'content-type');
		$response_boundary = '';
		if(preg_match('/boundary=([^;]+)/i', $content_type, $boundary_matches)){
			$response_boundary = trim($boundary_matches[1]);
		}
		
		// If no boundary found in response, try using the request boundary as fallback
		if(empty($response_boundary)){
			$response_boundary = $boundary;
		}
		
		// TODO:: Improve the response error handling for the batch responses.
		
		// Split by response boundary
		$parts = explode('--' . $response_boundary, $response_body);
		
		foreach($parts as $part){
			if(empty(trim($part)) || trim($part) === '--'){
				continue;
			}

			// Extract Content-ID (Google responds with "response-<original_id>")
			if(preg_match('/Content-ID:\s*<?response-([^>\s]+)>?/i', $part, $id_matches)){
				$content_id = trim($id_matches[1]);
			} elseif(preg_match('/Content-ID:\s*<?([^>\s]+)>?/i', $part, $id_matches)){
				// Fallback: try without "response-" prefix
				$content_id = trim($id_matches[1]);
			} else {
				continue;
			}
			
			// Extract JSON body - find the first { and match to the last }
			$json_start = strpos($part, '{');
			if($json_start !== false){
				$json_str = substr($part, $json_start);
				
				// Find the last closing brace to get complete JSON
				$last_brace = strrpos($json_str, '}');
				if($last_brace !== false){
					$json_str = substr($json_str, 0, $last_brace + 1);
				}
				
				$data = json_decode($json_str, true);
				if($data !== null){
					$results[$content_id] = $data;
				}
			}
		}

		return $results;
	}
}