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

/**
 * Get the WPML frontend domain for the current language.
 *
 * @return string|null The domain for the current language or null if WPML is not active
 */
function get_wpml_frontend_domain_for_current_lang() {
    // Check if WPML is active using more common detection methods
    if ( defined('WPML_PLUGIN_PATH') || function_exists('icl_get_languages') ) {
        $current_lang = isset($_GET['lang']) ? sanitize_text_field($_GET['lang']) : null;

        if ( ! $current_lang ) {
            $current_lang = apply_filters( 'wpml_admin_language', null );
        }
        if ( ! $current_lang ) {
            $current_lang = apply_filters( 'wpml_current_language', null );
        }

        // Try different WPML domain functions
        if ( function_exists('wpml_get_domain_for_language') ) {
            return wpml_get_domain_for_language( $current_lang );
        }
        
        // Alternative method using WPML settings
        if ( $current_lang && function_exists('icl_get_languages') ) {
            $languages = icl_get_languages('skip_missing=0');
            if ( isset($languages[$current_lang]) && !empty($languages[$current_lang]['url']) ) {
                return $languages[$current_lang]['url'];
            }
        }
    }

    return null;
}

/**
 * Get the current domain identifier from the Morningscore API.
 * Uses WPML frontend domain detection when available.
 *
 * @return string|WP_Error Domain identifier or error
 */
function rank_get_domain_identifier() {
    // Get current domain - try WPML frontend domain first
    $wpml_domain = get_wpml_frontend_domain_for_current_lang();
    
    if ( $wpml_domain ) {
        $site_domain = parse_url( $wpml_domain, PHP_URL_HOST );
        $domain_source = 'WPML frontend domain';
    } else {
        $site_domain = parse_url( home_url(), PHP_URL_HOST );
        $domain_source = 'WordPress home_url()';
    }
    
    
    // Fetch domains from API (no caching)
    $response = wp_remote_get( 'https://api.morningscore.io/v1/domains', array(
        'headers' => array(
            'Authorization' => 'Bearer ' . rank_get_api_key(),
            'Accept' => 'application/json',
        ),
        'timeout' => 15
    ));

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

    $response_code = wp_remote_retrieve_response_code( $response );
    if ( $response_code !== 200 ) {
        return new WP_Error(
            'api_error',
            'Failed to retrieve domains. Status code: ' . $response_code
        );
    }

    $domains = json_decode( wp_remote_retrieve_body( $response ), true );
    
    if ( ! is_array( $domains ) ) {
        return new WP_Error( 'api_error', 'Invalid response format when fetching domains' );
    }

    // Find the matching domain
    $domain_identifier = null;
    
    // Normalize the site domain by removing www if present
    $normalized_site_domain = preg_replace( '/^www\./', '', $site_domain );
    
    foreach ( $domains as $domain ) {
        if ( isset( $domain['domain'] ) ) {
            // Normalize the API domain by removing www if present
            $normalized_api_domain = preg_replace( '/^www\./', '', $domain['domain'] );
            
            // Compare the normalized domains
            if ( $normalized_api_domain === $normalized_site_domain ) {
                $domain_identifier = $domain['global_domain_identifier'];
                break;
            }
        }
    }

    if ( ! $domain_identifier ) {
        return new WP_Error( 'domain_not_found', 'Your website domain was not found in your Morningscore account' );
    }

    // No caching - always return fresh domain identifier
    return $domain_identifier;
}

/**
 * Get the AI credits status from the Morningscore API.
 * 
 * @return array|WP_Error Array with credit info or error
 */
function rank_get_ai_credits() {
    $response = wp_remote_get( 
        'https://api.morningscore.io/v1/credits/get-status?credit_type=credits.ai.monthly_ai_credits', 
        array(
            'headers' => array(
                'Authorization' => 'Bearer ' . rank_get_api_key(),
                'Accept' => 'application/json',
            ),
            'timeout' => 15
        )
    );

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

    $response_code = wp_remote_retrieve_response_code( $response );
    if ( $response_code !== 200 ) {
        return new WP_Error( 
            'api_error', 
            'Failed to retrieve AI credits. Status code: ' . $response_code
        );
    }

    $credits = json_decode( wp_remote_retrieve_body( $response ), true );
    
    if ( ! is_array( $credits ) || ! isset( $credits['limit'] ) || ! isset( $credits['usage'] ) ) {
        return new WP_Error( 'api_error', 'Invalid response format when fetching AI credits' );
    }

    return $credits;
}


/**
 * Get all issues for a specific health check category.
 * Filters out issues with URLs that don't exist in WordPress.
 *
 * @param string $issue_type The category key (e.g., 'missing_alt_tags')
 * @param int $limit Optional limit for number of issues to return
 * @param int $offset Optional offset for pagination
 * @return array|WP_Error Array of issues or error
 */
function rank_get_all_issues( $issue_type, $limit = null, $offset = null ) {
    $domain_identifier = rank_get_domain_identifier();
    if ( is_wp_error( $domain_identifier ) ) {
        return $domain_identifier;
    }

    // If limit and offset are provided, use chunked approach
    if ( $limit !== null && $offset !== null ) {
        return rank_get_issues_chunk( $issue_type, $limit, $offset, $domain_identifier );
    }

    // Start with first page to get total count
    $per_page = 100;
    $all_issues = array();
    $skipped_count = 0;
    $count = 0;
    $total_pages = 1;

    // Fetch all pages
    for ( $page = 1; $page <= $total_pages; $page++ ) {
        $url = "https://api.morningscore.io/v1/{$domain_identifier}/get_issues";
        $query_args = array(
            'filter.issue_type' => $issue_type,
            'filter.resolved' => 'false',
            'filter.ignored' => 'false',
            'pagination.page' => $page,
            'pagination.per_page' => $per_page
        );

        $response = wp_remote_get(
            add_query_arg( $query_args, $url ),
            array(
                'headers' => array(
                    'Authorization' => 'Bearer ' . rank_get_api_key(),
                    'Accept' => 'application/json',
                ),
                'timeout' => 60
            )
        );

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

        $response_code = wp_remote_retrieve_response_code( $response );
        if ( $response_code !== 200 ) {
            return new WP_Error(
                'api_error',
                'Failed to retrieve issues. Status code: ' . $response_code
            );
        }

        $result = json_decode( wp_remote_retrieve_body( $response ), true );
        
        if ( ! is_array( $result ) || ! isset( $result['data'] ) || ! is_array( $result['data'] ) ) {
            return new WP_Error( 'api_error', 'Invalid response format when fetching issues' );
        }

        // Get count from first response and calculate total pages
        if ( $page === 1 ) {
            if ( ! isset( $result['total'] ) ) {
                return new WP_Error( 'api_error', 'Invalid response format - missing total count' );
            }
            $count = intval( $result['total'] );
            $total_pages = ceil( $count / $per_page );
            
            // If no issues found, return empty array
            if ( $count === 0 ) {
                return array();
            }
        }

        // Light filtering: only check if URL belongs to this WordPress installation (host/path match)
        // Don't do expensive HTTP validation here - that will happen during processing if needed
        $wp_home_url = home_url();
        $wp_base_path = parse_url($wp_home_url, PHP_URL_PATH);
        $wp_host = parse_url($wp_home_url, PHP_URL_HOST);
        
        // Normalize host for comparison (remove www. prefix)
        $normalize_host = function($host) {
            if (empty($host)) return '';
            $host = strtolower($host);
            if (strpos($host, 'www.') === 0) {
                return substr($host, 4);
            }
            return $host;
        };
        $wp_host_normalized = $normalize_host($wp_host);
        
        foreach ($result['data'] as $issue) {
            // Get the page URL from the issue data
            $page_url = null;
            if (isset($issue['page']) && isset($issue['page']['url'])) {
                $page_url = $issue['page']['url'];
            } elseif (isset($issue['page']) && isset($issue['page']['path_full'])) {
                $page_url = $issue['page']['path_full'];
            } elseif (isset($issue['url'])) {
                $page_url = $issue['url'];
            }

            if (!empty($page_url)) {
                // Quick host check only - no expensive HTTP validation
                // Be very lenient with path matching to support WPML and other multilingual setups
                $url_host = parse_url($page_url, PHP_URL_HOST);
                
                // Normalize and check host match (handle www vs non-www)
                $url_host_normalized = $normalize_host($url_host);
                
                // Skip only if host clearly doesn't match
                // Don't do strict path matching here - WPML and other multilingual plugins
                // can have different path structures (e.g., /da/, /en/) that are still valid
                if ($url_host_normalized !== $wp_host_normalized) {
                    $skipped_count++;
                    continue;
                }
                
                // Host matches - add the issue
                // Path validation will happen later during processing if needed
                // This allows WPML language prefixes and other path variations to pass through
                $all_issues[] = $issue;
            } else {
                // No URL found in the issue data, add it anyway
                $all_issues[] = $issue;
            }
        }

        // Add a short delay between requests
        if ( $page < $total_pages - 1 ) {
            sleep( 0.2 );
        }
    }


    return $all_issues;
}

/**
 * Get a chunk of issues for pagination.
 *
 * @param string $issue_type The issue type to fetch
 * @param int $limit Number of issues to return
 * @param int $offset Starting offset
 * @param string $domain_identifier Domain identifier
 * @return array Array with 'issues' and 'total' keys
 */
function rank_get_issues_chunk( $issue_type, $limit, $offset, $domain_identifier ) {
    // Calculate which API pages we need to fetch
    $api_per_page = 100;
    $start_page = floor( $offset / $api_per_page ) + 1;
    $end_page = ceil( ( $offset + $limit ) / $api_per_page );
    
    $all_issues = array();
    $skipped_count = 0;
    $total_count = 0;
    
    // Fetch the required API pages
    for ( $page = $start_page; $page <= $end_page; $page++ ) {
        $url = "https://api.morningscore.io/v1/{$domain_identifier}/get_issues";
        $query_args = array(
            'filter.issue_type' => $issue_type,
            'filter.resolved' => 'false',
            'filter.ignored' => 'false',
            'pagination.page' => $page,
            'pagination.per_page' => $api_per_page
        );

        $response = wp_remote_get(
            add_query_arg( $query_args, $url ),
            array(
                'headers' => array(
                    'Authorization' => 'Bearer ' . rank_get_api_key(),
                    'Accept' => 'application/json',
                ),
                'timeout' => 60
            )
        );

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

        $response_code = wp_remote_retrieve_response_code( $response );
        if ( $response_code !== 200 ) {
            return new WP_Error(
                'api_error',
                'Failed to retrieve issues. Status code: ' . $response_code
            );
        }

        $result = json_decode( wp_remote_retrieve_body( $response ), true );
        
        if ( ! is_array( $result ) || ! isset( $result['data'] ) || ! is_array( $result['data'] ) ) {
            return new WP_Error( 'api_error', 'Invalid response format when fetching issues' );
        }

        // Capture total count from first page response
        if ( $page === $start_page && isset( $result['total'] ) ) {
            $total_count = intval( $result['total'] );
        }

        // Light filtering: only check if URL belongs to this WordPress installation (host/path match)
        // Don't do expensive HTTP validation here - that will happen during processing if needed
        $wp_home_url = home_url();
        $wp_base_path = parse_url($wp_home_url, PHP_URL_PATH);
        $wp_host = parse_url($wp_home_url, PHP_URL_HOST);
        
        // Normalize host for comparison (remove www. prefix)
        $normalize_host = function($host) {
            if (empty($host)) return '';
            $host = strtolower($host);
            if (strpos($host, 'www.') === 0) {
                return substr($host, 4);
            }
            return $host;
        };
        $wp_host_normalized = $normalize_host($wp_host);
        
        foreach ($result['data'] as $issue) {
            // Get the page URL from the issue data
            $page_url = null;
            if (isset($issue['page']) && isset($issue['page']['url'])) {
                $page_url = $issue['page']['url'];
            } elseif (isset($issue['page']) && isset($issue['page']['path_full'])) {
                $page_url = $issue['page']['path_full'];
            } elseif (isset($issue['url'])) {
                $page_url = $issue['url'];
            }

            if (!empty($page_url)) {
                // Quick host check only - no expensive HTTP validation
                // Be very lenient with path matching to support WPML and other multilingual setups
                $url_host = parse_url($page_url, PHP_URL_HOST);
                
                // Normalize and check host match (handle www vs non-www)
                $url_host_normalized = $normalize_host($url_host);
                
                // Skip only if host clearly doesn't match
                // Don't do strict path matching here - WPML and other multilingual plugins
                // can have different path structures (e.g., /da/, /en/) that are still valid
                if ($url_host_normalized !== $wp_host_normalized) {
                    $skipped_count++;
                    continue;
                }
                
                // Host matches - add the issue
                // Path validation will happen later during processing if needed
                // This allows WPML language prefixes and other path variations to pass through
                $all_issues[] = $issue;
            } else {
                // No URL found in the issue data, add it anyway
                $all_issues[] = $issue;
            }
        }
    }
    
    // Apply offset and limit to the filtered results
    $chunk_start = $offset % $api_per_page;
    if ( $start_page < $end_page ) {
        // We fetched multiple pages, need to calculate the correct offset
        $chunk_start = $offset - ( ( $start_page - 1 ) * $api_per_page );
    }
    
    $result_issues = array_slice( $all_issues, $chunk_start, $limit );
    
    // Always return both issues and total count since every API response includes the total
    return array(
        'issues' => $result_issues,
        'total' => $total_count
    );
}

/**
 * Get AI fix for a specific issue.
 *
 * @param string $rank_id The rank ID of the issue
 * @param string $domain_identifier Optional domain identifier (if not provided, it will be fetched)
 * @return array|WP_Error The AI fix or error
 */
function rank_get_ai_fix( $rank_id, $domain_identifier = null ) {
    if ( ! $domain_identifier ) {
        $domain_identifier = rank_get_domain_identifier();
        if ( is_wp_error( $domain_identifier ) ) {
            return $domain_identifier;
        }
    }

    $url = "https://api.morningscore.io/v1/{$domain_identifier}/get_ai_fix";
    
    $request_body = array(
        'rank_id' => $rank_id,
        'fresh_data' => null
    );
    
    // Initial API call to get task_id
    $response = wp_remote_post(
        $url,
        array(
            'headers' => array(
                'Authorization' => 'Bearer ' . rank_get_api_key(),
                'Content-Type' => 'application/json',
                'Accept' => 'application/json',
            ),
            'body' => wp_json_encode($request_body),
            'timeout' => 15 // Reduced timeout since we're just getting a task ID
        )
    );

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

    $response_code = wp_remote_retrieve_response_code( $response );
    $response_body = wp_remote_retrieve_body( $response );
    
    $decoded_body = json_decode( $response_body, true );

    if ( $response_code !== 200 ) {
        // Specifically check for "OUT_OF_CREDITS"
        if ( isset( $decoded_body['message'] ) && $decoded_body['message'] === 'OUT_OF_CREDITS' ) {
            return new WP_Error(
                'out_of_credits',
                'AI credits exhausted. API Message: OUT_OF_CREDITS',
                array( 'status_code' => $response_code ) // Pass status code
            );
        }
        
        // Handle other API errors
        $error_message = isset( $decoded_body['message'] ) ? $decoded_body['message'] : 'Failed to retrieve AI fix.';
        return new WP_Error(
            'api_error',
            "Failed to retrieve AI fix. Status code: $response_code. Message: $error_message",
            array( 'status_code' => $response_code ) // Pass status code
        );
    }

    // Check if we have a task_id in the response
    if ( ! is_array( $decoded_body ) || ! isset( $decoded_body['task_id'] ) ) {
        return new WP_Error(
            'api_error',
            'Invalid response format when fetching AI fix - missing task_id.',
            array( 'status_code' => $response_code )
        );
    }

    $task_id = $decoded_body['task_id'];
    
    // Poll for the result
    $result = rank_get_ai_fix_status($task_id);
    
    return $result;
}

/**
 * Poll for AI fix status until completion or timeout.
 *
 * @param string $task_id The task ID to check
 * @param int $max_attempts Maximum number of polling attempts (default: 18 - covers ~35 seconds with 2-second intervals)
 * @param int $poll_interval Interval between polls in seconds (default: 2)
 * @return array|WP_Error The AI fix result or error
 */
function rank_get_ai_fix_status($task_id, $max_attempts = 18, $poll_interval = 2) {
    // Correct endpoint URL format for status check
    $status_url = "https://api.morningscore.io/v1/get_ai_fix_status/{$task_id}";
    
    for ($attempt = 1; $attempt <= $max_attempts; $attempt++) {
        // Add retry logic for each poll attempt
        $poll_result = rank_api_request_with_retry($status_url, 3);
        
        if (is_wp_error($poll_result)) {
            return $poll_result;
        }
        
        $response_code = $poll_result['response_code'];
        $result = $poll_result['data'];
        
        if ($response_code !== 200) {
            return new WP_Error(
                'api_error',
                "Failed to retrieve AI fix status. Status code: $response_code.",
                array('status_code' => $response_code)
            );
        }
        
        if (!is_array($result)) {
            return new WP_Error(
                'api_error',
                'Invalid response format when fetching AI fix status.',
                array('status_code' => $response_code)
            );
        }

        // Check if the task is completed or failed
        Rank_Processor::add_debug_log("=== AI FIX STATUS POLL (attempt $attempt) ===");
        Rank_Processor::add_debug_log("Task ID: $task_id");
        Rank_Processor::add_debug_log("Response status: " . ($result['status'] ?? 'NOT_SET'));
        Rank_Processor::add_debug_log("Full response: " . json_encode($result));
        
        if (isset($result['status'])) {
            if ($result['status'] === 'completed') {
                // Format the response to match the expected structure
                if (isset($result['data']) && isset($result['data']['content'])) {
                    Rank_Processor::add_debug_log("AI fix completed successfully. Content: " . substr(json_encode($result['data']['content']), 0, 200));
                    return array(
                        'content' => $result['data']['content'],
                        'progress' => $result['data']['progress'],
                        'task_id' => $result['data']['task_id'],
                        'identifier' => $result['data']['identifier']
                    );
                } else {
                    // Extract any error message from the response
                    $error_message = 'The AI had an error. Skipping this fix.';
                    
                    // Check if there's a more specific message in the response
                    if (isset($result['data']['message'])) {
                        $error_message = $result['data']['message'];
                    } elseif (isset($result['message'])) {
                        $error_message = $result['message'];
                    } elseif (isset($result['data']['error'])) {
                        $error_message = $result['data']['error'];
                    } elseif (isset($result['error'])) {
                        $error_message = $result['error'];
                    }
                    
                    Rank_Processor::add_debug_log("AI fix completed but no content. Error: " . $error_message);
                    Rank_Processor::add_debug_log("Response data structure: " . json_encode(array_keys($result['data'] ?? [])));
                    
                    return new WP_Error(
                        'api_error',
                        $error_message,
                        array('status_code' => $response_code, 'response' => $result)
                    );
                }
            } elseif ($result['status'] === 'failed') {
                // Handle failed status explicitly
                // When status is 'failed', we stop polling immediately as there's nothing more to do
                $error_message = 'RANK AI Could not find a fix';
                
                // Check if there's a more specific message in the response
                if (isset($result['data']['message'])) {
                    $error_message = $result['data']['message'];
                } elseif (isset($result['message'])) {
                    $error_message = $result['message'];
                } elseif (isset($result['data']['error'])) {
                    $error_message = $result['data']['error'];
                } elseif (isset($result['error'])) {
                    $error_message = $result['error'];
                }
                
                Rank_Processor::add_debug_log("AI fix FAILED. Error: " . $error_message);
                
                // Return error and stop polling - this exits the function and breaks the polling loop
                return new WP_Error(
                    'api_failed',
                    $error_message,
                    array('status_code' => $response_code, 'response' => $result)
                );
            } else {
                Rank_Processor::add_debug_log("AI fix still pending. Status: " . $result['status']);
            }
        } else {
            Rank_Processor::add_debug_log("No status field in response");
        }

        // If not completed, wait before polling again
        if ($attempt < $max_attempts) {
            sleep($poll_interval);
        }
    }

    // If we've reached the maximum number of attempts without completion
    $timeout_seconds = $max_attempts * $poll_interval;
    return new WP_Error(
        'api_timeout',
        "Timed out waiting for AI fix to complete after {$timeout_seconds} seconds. The API did not return a completed or failed status.",
        array('task_id' => $task_id)
    );
}

/**
 * Make an API request with retry logic.
 *
 * @param string $url The URL to request
 * @param int $max_retries Maximum number of retries (default: 3)
 * @param array $args Additional arguments for wp_remote_get
 * @return array|WP_Error The response data or error
 */
function rank_api_request_with_retry($url, $max_retries = 3, $args = array()) {
    $default_args = array(
        'headers' => array(
            'Authorization' => 'Bearer ' . rank_get_api_key(),
            'Accept' => 'application/json',
        ),
        'timeout' => 60
    );
    
    $request_args = wp_parse_args($args, $default_args);
    
    $retry_count = 0;
    $last_error = null;
    
    while ($retry_count < $max_retries) {
        $response = wp_remote_get($url, $request_args);
        
        if (!is_wp_error($response)) {
            $response_code = wp_remote_retrieve_response_code($response);
            $response_body = wp_remote_retrieve_body($response);
            $decoded_body = json_decode($response_body, true);
            
            // If we got a valid response, return it
            if ($response_code === 200 && $decoded_body) {
                return array(
                    'response_code' => $response_code,
                    'data' => $decoded_body
                );
            }
        }
        
        // Store the last error
        $last_error = is_wp_error($response) ? $response : new WP_Error(
            'api_error',
            "API request failed with status code: $response_code",
            array('status_code' => $response_code)
        );
        
        // Increment retry count and wait before retrying
        $retry_count++;
        if ($retry_count < $max_retries) {
            sleep(1); // Wait 1 second between retries
        }
    }
    
    // If we've exhausted all retries, return the last error
    return $last_error;
}

/**
 * Normalize URL for consistent comparison across the system.
 * Handles URL encoding and trailing slashes.
 *
 * @param string $url The URL to normalize
 * @return string The normalized URL
 */
function rank_normalize_url($url) {
    if (empty($url)) {
        return '';
    }
    
    // Get the current WordPress site domain
    $wp_host = parse_url(home_url(), PHP_URL_HOST);
    $url_host = parse_url($url, PHP_URL_HOST);
    
    // Skip normalization for external URLs (different domain)
    // These could be tracking URLs, CDN images, affiliate links, etc.
    // Preserving them exactly ensures accurate duplicate detection
    if ($url_host && $wp_host && $url_host !== $wp_host) {
        // External URL - preserve exactly as-is
        return $url;
    }
    
    // Only normalize internal URLs (same domain)
    $normalized = urldecode($url);
    
    // Remove trailing slash for consistency (except for root URLs)
    $normalized = rtrim($normalized, '/');
    
    // If it's just the protocol and domain (e.g., "https://example.com"), keep it as is
    // Otherwise, ensure we have a clean URL without trailing slash
    if (preg_match('#^https?://[^/]+$#', $normalized)) {
        // Root URL - no trailing slash needed
        return $normalized;
    }
    
    return $normalized;
}

/**
 * Clear the "already fixed" cache for a specific issue
 * This should be called after successfully storing a fix to ensure subsequent checks see the new data
 *
 * @param string $issue_type The type of issue
 * @param string $page_url The URL of the page
 * @param string $issue_identifier Optional. The specific identifier for the issue
 */
function rank_clear_fixed_status_cache($issue_type, $page_url, $issue_identifier = null) {
    // Access the global cache
    global $rank_fixed_status_cache;
    
    if (!isset($rank_fixed_status_cache)) {
        return; // Nothing to clear
    }
    
    // Recreate the same cache key logic as rank_is_issue_already_fixed
    $normalized_page_url = rank_normalize_url($page_url);
    $normalized_identifier = rank_normalize_url($issue_identifier);
    $cache_key = md5($issue_type . '|' . $normalized_page_url . '|' . $normalized_identifier);
    
    // Clear the cache entry
    if (isset($rank_fixed_status_cache[$cache_key])) {
        unset($rank_fixed_status_cache[$cache_key]);
        Rank_Processor::add_debug_log("Cache cleared for: $issue_type | $page_url | " . ($issue_identifier ? basename($issue_identifier) : 'N/A'));
    }
}

/**
 * Check if an issue has already been fixed locally.
 *
 * @param string $issue_type The type of issue (e.g., 'missing_alt_tags')
 * @param string $page_url The URL of the page with the issue
 * @param string $issue_identifier The specific identifier for the issue (e.g., image URL)
 * @return bool True if the issue has already been fixed locally
 */
function rank_is_issue_already_fixed($issue_type, $page_url, $issue_identifier) {
    // Use global cache instead of static so it can be cleared from other functions
    global $rank_attachment_id_cache, $rank_fixed_status_cache, $rank_post_id_cache, $rank_option_cache;
    
    if (!isset($rank_attachment_id_cache)) {
        $rank_attachment_id_cache = [];
    }
    if (!isset($rank_fixed_status_cache)) {
        $rank_fixed_status_cache = [];
    }
    if (!isset($rank_post_id_cache)) {
        $rank_post_id_cache = [];
    }
    if (!isset($rank_option_cache)) {
        $rank_option_cache = [];
    }
    
    // Keep references for backward compatibility with existing code
    $attachment_id_cache = &$rank_attachment_id_cache;
    $fixed_status_cache = &$rank_fixed_status_cache;
    $post_id_cache = &$rank_post_id_cache;
    $option_cache = &$rank_option_cache;
    
    // Debug log the function call
    // Check if issue is fixed
    
    // Normalize URLs first for consistent caching
    $normalized_page_url_for_cache = rank_normalize_url($page_url);
    $normalized_identifier_for_cache = rank_normalize_url($issue_identifier);
    
    // Create a unique cache key using normalized URLs
    $cache_key = md5($issue_type . '|' . $normalized_page_url_for_cache . '|' . $normalized_identifier_for_cache);
    
    // Check if we already have a cached result
    if (isset($fixed_status_cache[$cache_key])) {
        // Using cached result
        return $fixed_status_cache[$cache_key];
    }
    
    // Different logic based on issue type
    $issue_config = rank_get_issue_type_config($issue_type);
    
    if (!$issue_config) {
        // No issue config found
        $fixed_status_cache[$cache_key] = false;
        return false;
    }
    
    // Process issue config
    
    // Get post ID from URL (with caching)
    if (isset($post_id_cache[$page_url])) {
        $post_id = $post_id_cache[$page_url];
    } else {
        $post_id = url_to_postid($page_url);
        if (!$post_id) {
            // Try enhanced URL to post ID conversion
            $post_id = Rank_Processor::enhanced_url_to_postid($page_url);
        }
        $post_id_cache[$page_url] = $post_id;
    }
    
    if ($issue_config['handler_type'] === 'database_override') {
        global $wpdb;
        $table_name = $wpdb->prefix . RANK_TABLE_NAME;
        $db_override_type = '';
        
        // Map issue type to database override type
        switch ($issue_type) {
            case 'missing_meta_description':
            case 'short_meta_description':
            case 'long_meta_description':
                $db_override_type = 'meta_description';
                
                // For meta descriptions, check based on original_page_url (like alt tags)
                // This handles multilingual sites where same post_id can have different URLs
                if (!empty($page_url)) {
                    $normalized_page_url = rank_normalize_url($page_url);
                    $page_url_with_slash = $normalized_page_url . '/';
                    
                    Rank_Processor::add_debug_log("=== CHECKING IF ALREADY FIXED (META) ===");
                    Rank_Processor::add_debug_log("Original page_url: $page_url");
                    Rank_Processor::add_debug_log("Normalized page_url: $normalized_page_url");
                    
                    // Check for: normalized, normalized+slash, AND original raw URL
                    $query = $wpdb->prepare(
                        "SELECT COUNT(*) FROM `$table_name`
                         WHERE (original_page_url = %s OR original_page_url = %s OR original_page_url = %s)
                         AND override_type = %s",
                        $normalized_page_url, $page_url_with_slash, $page_url,
                        $db_override_type
                    );
                    
                    Rank_Processor::add_debug_log("SQL Query: " . $wpdb->remove_placeholder_escape($query));
                    $count = $wpdb->get_var($query);
                    Rank_Processor::add_debug_log("Query result count: $count");
                    Rank_Processor::add_debug_log("Already fixed: " . ($count > 0 ? 'YES' : 'NO'));
                    Rank_Processor::add_debug_log("=== END CHECKING IF ALREADY FIXED (META) ===");
                    
                    $fixed_status_cache[$cache_key] = ($count > 0);
                    return $fixed_status_cache[$cache_key];
                }
                break;
            case 'missing_title_tag':
            case 'short_title_tag':
            case 'long_title_tag':
                $db_override_type = 'title';
                
                // For titles, also check based on original_page_url
                if (!empty($page_url)) {
                    $normalized_page_url = rank_normalize_url($page_url);
                    $page_url_with_slash = $normalized_page_url . '/';
                    
                    Rank_Processor::add_debug_log("=== CHECKING IF ALREADY FIXED (TITLE) ===");
                    Rank_Processor::add_debug_log("Original page_url: $page_url");
                    Rank_Processor::add_debug_log("Normalized page_url: $normalized_page_url");
                    
                    // Check for: normalized, normalized+slash, AND original raw URL
                    $query = $wpdb->prepare(
                        "SELECT COUNT(*) FROM `$table_name`
                         WHERE (original_page_url = %s OR original_page_url = %s OR original_page_url = %s)
                         AND override_type = %s",
                        $normalized_page_url, $page_url_with_slash, $page_url,
                        $db_override_type
                    );
                    
                    Rank_Processor::add_debug_log("SQL Query: " . $wpdb->remove_placeholder_escape($query));
                    $count = $wpdb->get_var($query);
                    Rank_Processor::add_debug_log("Query result count: $count");
                    Rank_Processor::add_debug_log("Already fixed: " . ($count > 0 ? 'YES' : 'NO'));
                    Rank_Processor::add_debug_log("=== END CHECKING IF ALREADY FIXED (TITLE) ===");
                    
                    $fixed_status_cache[$cache_key] = ($count > 0);
                    return $fixed_status_cache[$cache_key];
                }
                break;
            case 'missing_alt_tags':
                $db_override_type = 'image_alt';
                
                // For alt tags, check based on original_page_url and image URL
                // This is more reliable than post_id which can fail for various reasons
                if (!empty($page_url) && $issue_config['identifier_is_url'] && !empty($issue_identifier)) {
                    // Normalize URLs for comparison - handles encoding and trailing slashes
                    $normalized_page_url = rank_normalize_url($page_url);
                    $normalized_image_url = rank_normalize_url($issue_identifier);
                    
                    // Create URL variations to handle trailing slash differences in database
                    $page_url_with_slash = $normalized_page_url . '/';
                    $image_url_with_slash = $normalized_image_url . '/';
                    
                    // Debug logging
                    Rank_Processor::add_debug_log("=== CHECKING IF ALREADY FIXED ===");
                    Rank_Processor::add_debug_log("Original page_url: $page_url");
                    Rank_Processor::add_debug_log("Normalized page_url: $normalized_page_url");
                    Rank_Processor::add_debug_log("Page URL with slash: $page_url_with_slash");
                    Rank_Processor::add_debug_log("Original image_url: $issue_identifier");
                    Rank_Processor::add_debug_log("Normalized image_url: $normalized_image_url");
                    Rank_Processor::add_debug_log("Image URL with slash: $image_url_with_slash");
                    
                    // Check URL-based storage using original_page_url instead of post_id
                    // Check for: normalized, normalized+slash, AND original raw URL
                    // This ensures we find records regardless of how they were saved
                    $query = $wpdb->prepare(
                        "SELECT COUNT(*) FROM `$table_name`
                         WHERE (original_page_url = %s OR original_page_url = %s OR original_page_url = %s)
                         AND override_type = %s
                         AND (image_url = %s OR image_url = %s OR image_url = %s)",
                        $normalized_page_url, $page_url_with_slash, $page_url,
                        $db_override_type,
                        $normalized_image_url, $image_url_with_slash, $issue_identifier
                    );
                    
                    Rank_Processor::add_debug_log("SQL Query: " . $wpdb->remove_placeholder_escape($query));
                    $count = $wpdb->get_var($query);
                    Rank_Processor::add_debug_log("Query result count: $count");
                    Rank_Processor::add_debug_log("Already fixed: " . ($count > 0 ? 'YES' : 'NO'));
                    Rank_Processor::add_debug_log("=== END CHECKING IF ALREADY FIXED ===");
                    
                    $fixed_status_cache[$cache_key] = ($count > 0);
                    return $fixed_status_cache[$cache_key];
                }
                
                // If we don't have the required data for alt tags, assume not fixed
                $fixed_status_cache[$cache_key] = false;
                return false;
        }
        
        // All database_override types (meta, title, alt) now use original_page_url checks above
        // If we reach here, it means we don't have the required data
        Rank_Processor::add_debug_log("Missing required data for detection check");
        $fixed_status_cache[$cache_key] = false;
        return false;
    }
    elseif ($issue_config['handler_type'] === 'option_suggestion') {
        // For broken links
        $option_name = '';
        if ($issue_type === 'broken_links_internal') {
            $option_name = 'rank_internal_url_replacements';
        } elseif ($issue_type === 'broken_links_external') {
            $option_name = 'rank_external_url_replacements';
        }
        
        if (!empty($option_name) && !empty($issue_identifier) && !empty($page_url)) {
            // Cache option values to avoid repeated get_option calls
            if (!isset($option_cache[$option_name])) {
                $option_cache[$option_name] = get_option($option_name, []);
            }
            $replacements = $option_cache[$option_name];
            
            if (!is_array($replacements)) {
                $fixed_status_cache[$cache_key] = false;
                return false;
            }
            
            // Normalize URLs to handle encoding and trailing slashes before creating hash
            $normalized_page_url = rank_normalize_url($page_url);
            $normalized_issue_identifier = rank_normalize_url($issue_identifier);
            
            // Check using the same composite key format as storage
            // Format: broken_url::context::md5(page_url)
            // Use normalized URL for consistent hash generation
            $context_hash = md5($normalized_page_url);
            $composite_key = $normalized_issue_identifier . '::context::' . $context_hash;
            
            // Check if this specific broken URL + page context has a replacement
            $result = isset($replacements[$composite_key]);
            $fixed_status_cache[$cache_key] = $result;
            return $result;
        }
    }
    
    $fixed_status_cache[$cache_key] = false;
    return false;
}

/**
 * Get health issue counts from the new /overall endpoint.
 * Simple and fast - no sampling or fix status checking.
 *
 * @return array|WP_Error Array with issue counts by category and crawled_at timestamp or error
 */
function rank_get_health_issues_overview_counts() {
    $domain_identifier = rank_get_domain_identifier();
    if (is_wp_error($domain_identifier)) {
        return $domain_identifier;
    }
    
    // Single API call to get all issue counts
    $url = "https://api.morningscore.io/v1/{$domain_identifier}/overall";
    $response = wp_remote_get($url, array(
        'headers' => array(
            'Authorization' => 'Bearer ' . rank_get_api_key(),
            'Accept' => 'application/json',
        ),
        'timeout' => 30
    ));
    
    if (is_wp_error($response)) {
        return $response;
    }
    
    $response_code = wp_remote_retrieve_response_code($response);
    if ($response_code !== 200) {
        return new WP_Error('api_error', 'Failed to retrieve overview. Status code: ' . $response_code);
    }
    
    $result = json_decode(wp_remote_retrieve_body($response), true);
    if (!is_array($result)) {
        return new WP_Error('api_error', 'Invalid overview response format - not an array');
    }
    
    // Check if we have the expected structure
    if (!isset($result['categories'])) {
        return new WP_Error('api_error', 'Invalid overview response format - missing categories');
    }
    
    // Extract crawled_at timestamp and crawl_status
    $crawled_at = isset($result['crawled_at']) ? $result['crawled_at'] : null;
    $crawl_status = isset($result['crawl_status']) ? $result['crawl_status'] : 'ready';
    
    // Get all known categories for labels
    $all_categories = rank_get_health_check_categories();
    $issue_counts = array();
    
    // Process each category's problems
    foreach ($result['categories'] as $category => $category_data) {
        if (!isset($category_data['problems']) || !is_array($category_data['problems'])) {
            continue;
        }
        
        foreach ($category_data['problems'] as $problem) {
            $validator = $problem['validator'];
            $issues_count = intval($problem['issues_num']);
            
            // Handle empty validator field
            if (empty($validator)) {
                continue; // Skip empty validators
            }
            
            // Map validator to internal issue type (direct mapping)
            $issue_type = $validator;
            $label = isset($all_categories[$issue_type]) ? $all_categories[$issue_type] : $issue_type;
            
            $issue_counts[$issue_type] = array(
                'label' => $label,
                'count' => $issues_count < 0 ? 0 : $issues_count
            );
        }
    }
    
    // Ensure all known issue types are included (even if not in API response)
    foreach ($all_categories as $issue_type => $label) {
        if (!isset($issue_counts[$issue_type])) {
            $issue_counts[$issue_type] = array(
                'label' => $label,
                'count' => 0
            );
        }
    }
    
    return array(
        'issues' => $issue_counts,
        'crawled_at' => $crawled_at,
        'crawl_status' => $crawl_status
    );
}



/**
 * Check if the crawled_at timestamp is older than 30 days.
 *
 * @param string $crawled_at ISO 8601 timestamp string (e.g., "2025-07-09T09:54:04.016Z")
 * @return bool True if older than 30 days, false otherwise
 */
function rank_is_crawl_data_too_old($crawled_at) {
    if (empty($crawled_at)) {
        return false; // Consider missing timestamp as fresh
    }
    
    // Parse the ISO 8601 timestamp
    $crawled_timestamp = strtotime($crawled_at);
    if ($crawled_timestamp === false) {
        return false; // Invalid timestamp format, consider fresh
    }
    
    // Calculate 30 days ago from now
    $thirty_days_ago = time() - (30 * 24 * 60 * 60);
    
    // Return true if crawled_at is older than 30 days
    return $crawled_timestamp < $thirty_days_ago;
}


/**
 * Check if a URL is accessible and doesn't redirect.
 *
 * @param string $url The URL to check
 * @return array Result with 'exists' and 'redirects' flags
 */
function rank_validate_url($url) {
    // Use static cache to avoid checking the same URL multiple times
    static $url_cache = array();
    
    // Return cached result if available
    if (isset($url_cache[$url])) {
        Rank_Processor::add_debug_log("URL validation cache hit for: $url");
        return $url_cache[$url];
    }
    
    Rank_Processor::add_debug_log("=== URL VALIDATION START ===");
    Rank_Processor::add_debug_log("Validating URL: $url");
    
    $result = array(
        'exists' => false,
        'redirects' => false,
        'status' => 0,
        'method_used' => ''
    );
    
    // Check if URL belongs to this WordPress installation (subdirectory support)
    $wp_home_url = home_url();
    $wp_base_path = parse_url($wp_home_url, PHP_URL_PATH);
    $wp_host = parse_url($wp_home_url, PHP_URL_HOST);
    
    $url_path = parse_url($url, PHP_URL_PATH);
    $url_host = parse_url($url, PHP_URL_HOST);
    
    Rank_Processor::add_debug_log("WordPress base path: " . ($wp_base_path ?: '/'));
    Rank_Processor::add_debug_log("WordPress host: " . $wp_host);
    Rank_Processor::add_debug_log("URL path: " . ($url_path ?: '/'));
    Rank_Processor::add_debug_log("URL host: " . $url_host);
    
    // Normalize host for comparison (remove www. prefix) to handle www vs non-www
    $normalize_host = function($host) {
        if (empty($host)) return '';
        $host = strtolower($host);
        // Remove www. prefix for comparison
        if (strpos($host, 'www.') === 0) {
            return substr($host, 4);
        }
        return $host;
    };
    $wp_host_normalized = $normalize_host($wp_host);
    $url_host_normalized = $normalize_host($url_host);
    
    // Check if host matches (with www normalization)
    if ($url_host_normalized !== $wp_host_normalized) {
        Rank_Processor::add_debug_log("URL host does not match WordPress host (normalized: $url_host_normalized !== $wp_host_normalized) - skipping");
        $result['exists'] = false;
        $result['method_used'] = 'HOST_MISMATCH';
        $url_cache[$url] = $result;
        Rank_Processor::add_debug_log("=== URL VALIDATION END (HOST MISMATCH) ===");
        return $result;
    }
    
    // Don't do strict path matching here - WPML and other multilingual plugins
    // can have different path structures (e.g., /da/, /en/) that are still valid
    // Path validation will be handled by WordPress url_to_postid() later if needed
    Rank_Processor::add_debug_log("Host matches (normalized) - proceeding with HTTP validation");
    
    // Step 1: Try HEAD request first (fast check)
    Rank_Processor::add_debug_log("Attempting HEAD request...");
    $head_response = wp_remote_head($url, array(
        'timeout' => 10, // Increased timeout
        'redirection' => 0, // Don't follow redirects
        'user-agent' => 'RANK AI WordPress Plugin/1.0',
        'sslverify' => false // Disable SSL verification to handle sites with certificate issues
    ));
    
    if (is_wp_error($head_response)) {
        $error_message = $head_response->get_error_message();
        Rank_Processor::add_debug_log("HEAD request failed: $error_message");
    } else {
        $head_status = wp_remote_retrieve_response_code($head_response);
        $head_headers = wp_remote_retrieve_headers($head_response);
        
        Rank_Processor::add_debug_log("HEAD response status: $head_status");
        Rank_Processor::add_debug_log("HEAD response headers: " . print_r($head_headers->getAll(), true));
        
        // Check if HEAD request was successful
        if ($head_status >= 200 && $head_status < 300) {
            $result['exists'] = true;
            $result['status'] = $head_status;
            $result['method_used'] = 'HEAD';
            
            Rank_Processor::add_debug_log("HEAD request successful - URL exists");
            Rank_Processor::add_debug_log("=== URL VALIDATION END (HEAD SUCCESS) ===");
            
            // Cache and return successful result
            $url_cache[$url] = $result;
            return $result;
        } else if ($head_status >= 300 && $head_status < 400) {
            $result['redirects'] = true;
            $result['status'] = $head_status;
            Rank_Processor::add_debug_log("HEAD request shows redirect (status: $head_status)");
        } else {
            Rank_Processor::add_debug_log("HEAD request unsuccessful (status: $head_status)");
        }
    }
    
    // Step 2: Fallback to GET request if HEAD failed or was inconclusive
    Rank_Processor::add_debug_log("Attempting GET request fallback...");
    $get_response = wp_remote_get($url, array(
        'timeout' => 10,
        'redirection' => 0, // Don't follow redirects
        'user-agent' => 'RANK AI WordPress Plugin/1.0',
        'sslverify' => false // Disable SSL verification to handle sites with certificate issues
    ));
    
    if (is_wp_error($get_response)) {
        $error_message = $get_response->get_error_message();
        Rank_Processor::add_debug_log("GET request failed: $error_message");
        $result['exists'] = false;
        $result['method_used'] = 'GET_FAILED';
    } else {
        $get_status = wp_remote_retrieve_response_code($get_response);
        $get_headers = wp_remote_retrieve_headers($get_response);
        
        Rank_Processor::add_debug_log("GET response status: $get_status");
        Rank_Processor::add_debug_log("GET response headers: " . print_r($get_headers->getAll(), true));
        
        $result['status'] = $get_status;
        $result['method_used'] = 'GET';
        
        // Check if it's a redirect (300-399)
        if ($get_status >= 300 && $get_status < 400) {
            $result['redirects'] = true;
            $result['exists'] = false;
            Rank_Processor::add_debug_log("GET request shows redirect (status: $get_status) - marking as non-existent");
        }
        // Check if it exists (200-299)
        else if ($get_status >= 200 && $get_status < 300) {
            $result['exists'] = true;
            Rank_Processor::add_debug_log("GET request successful - URL exists");
        }
        // Handle other status codes
        else {
            $result['exists'] = false;
            Rank_Processor::add_debug_log("GET request unsuccessful (status: $get_status) - marking as non-existent");
        }
    }
    
    // Final result logging
    $exists_text = $result['exists'] ? 'EXISTS' : 'DOES NOT EXIST';
    $redirects_text = $result['redirects'] ? 'REDIRECTS' : 'NO REDIRECT';
    Rank_Processor::add_debug_log("Final result: $exists_text, $redirects_text, Status: {$result['status']}, Method: {$result['method_used']}");
    Rank_Processor::add_debug_log("=== URL VALIDATION END ===");
    
    // Cache the result
    $url_cache[$url] = $result;
    
    return $result;
}
