<?php

/**
 * Enhanced Audit REST API Controller
 *
 * @package ProRank\SEO\Core\RestApi
 */

declare(strict_types=1);

namespace ProRank\SEO\Core\RestApi;

use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
use ProRank\SEO\Core\Audits\BasicAuditEngine;
use ProRank\SEO\Core\Audits\SiteAuditEngine;
use ProRank\SEO\Core\Helpers\LinkHelper;
use ProRank\SEO\Modules\Content\InternalLinking\TableRegistry;

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


/**
 * Enhanced Audit Controller
 */


class EnhancedAuditController extends BaseController {
    /**
     * Database table name for broken links
     * 
     * @var string
     */
    private string $broken_links_table;
    /**
     * Constructor
     */
    public function __construct() {
        parent::__construct();
        global $wpdb;
        $this->broken_links_table = $wpdb->prefix . 'prorank_broken_links';
    }

    /**
     * Check whether the current site has credentials to use ProRank.io cloud endpoints.
     */
    private function has_cloud_audit_auth(): bool {
        return false;
    }

    /**
     * Check whether the current site has access to non-local audit mode.
     */
    private function has_cloud_audit_license(): bool {
        return false;
    }

    /**
     * Determine the current non-local audit auth state for user-facing responses.
     */
    private function get_cloud_audit_auth_state(): string {
        return 'license_inactive';
    }

    /**
     * Build a user-facing non-local audit auth response payload.
     */
    private function get_cloud_auth_unavailable_payload(string $text_domain): array {
        return [
            'status' => 403,
            'message' => __('Only local site audits are available in the free plugin.', $text_domain),
            'error' => 'cloud_auth_unavailable',
            'service_error' => 'FREE_PACKAGE_ONLY',
            'auth_state' => 'license_inactive',
        ];
    }

    /**
     * Resolve requested audit mode from request.
     *
     * @return string local|cloud|auto
     */
    private function get_requested_mode(WP_REST_Request $request): string {
        return 'local';
    }

    /**
     * Normalize local crawler status payload to the dashboard contract.
     */
    private function normalize_local_status(array $status): array {
        $state = (string) ($status['state'] ?? 'idle');
        $stats = is_array($status['stats'] ?? null) ? $status['stats'] : [];
        $progress = is_array($status['progress'] ?? null) ? $status['progress'] : [];

        $critical = (int) ($stats['critical_issues'] ?? 0);
        $high = (int) ($stats['high_issues'] ?? 0);
        $medium = (int) ($stats['medium_issues'] ?? 0);
        $low = (int) ($stats['low_issues'] ?? 0);
        $total_issues = (int) ($stats['total_issues'] ?? ($critical + $high + $medium + $low));
        $total_urls = (int) ($stats['total_urls'] ?? 0);
        $passed = (int) ($stats['passed'] ?? 0);
        $total_checks = (int) ($stats['total_checks'] ?? max(0, $passed + $total_issues));

        if (empty($progress) && $state === 'completed') {
            $progress = [
                'total_urls' => $total_urls,
                'checked_urls' => $total_urls,
                'percentage' => $total_urls > 0 ? 100 : 0,
            ];
        }

        $percentage = (float) ($progress['percentage'] ?? 0);
        if (isset($progress['percent'])) {
            $percentage = (float) $progress['percent'];
        }
        $progress_total_urls = (int) ($progress['total_urls'] ?? 0);
        if ($total_urls <= 0 && $progress_total_urls > 0) {
            $total_urls = $progress_total_urls;
        }
        if ($total_issues <= 0) {
            $issues_list = is_array($status['issues'] ?? null) ? $status['issues'] : [];
            if (!empty($issues_list)) {
                $total_issues = count($issues_list);
            }
        }

        $can_start = !in_array($state, ['running', 'crawling', 'checking', 'analyzing', 'queued', 'pending'], true);

        return [
            'state' => $state,
            'status' => $state,
            'mode' => 'local',
            'audit_id' => $status['audit_id'] ?? null,
            'started_at' => $status['started_at'] ?? null,
            'completed_at' => $status['completed_at'] ?? null,
            'message' => $status['message'] ?? ($state === 'idle'
                ? __('No active local audit', 'prorank-seo')
                : __('Local audit status loaded', 'prorank-seo')),
            'score' => (int) ($status['score'] ?? ($status['overall_score'] ?? 0)),
            'overallScore' => (int) ($status['score'] ?? ($status['overall_score'] ?? 0)),
            'overall_score' => (int) ($status['score'] ?? ($status['overall_score'] ?? 0)),
            'totalUrls' => $total_urls,
            'total_urls' => $total_urls,
            'issues' => is_array($status['issues'] ?? null) ? $status['issues'] : [],
            'quick_fixes' => is_array($status['quick_fixes'] ?? null) ? $status['quick_fixes'] : [],
            'categories' => is_array($status['categories'] ?? null) ? $status['categories'] : [],
            'can_start' => $can_start,
            'stats' => [
                'critical_issues' => $critical,
                'high_issues' => $high,
                'medium_issues' => $medium,
                'low_issues' => $low,
                'warning_issues' => (int) ($stats['warning_issues'] ?? ($medium + $low)),
                'total_issues' => $total_issues,
                'unique_issues' => (int) ($stats['unique_issues'] ?? $total_issues),
                'total_instances' => (int) ($stats['total_instances'] ?? $total_issues),
                'total_urls' => $total_urls,
                'passed' => $passed,
                'total_checks' => $total_checks,
            ],
            'progress' => [
                'percent' => $percentage,
                'percentage' => $percentage,
                'total_urls' => (int) ($progress['total_urls'] ?? $total_urls),
                'checked_urls' => (int) ($progress['checked_urls'] ?? ($state === 'completed' ? $total_urls : 0)),
                'issues_found' => $total_issues,
            ],
        ];
    }

    private function normalize_local_history_entry(array $audit): array {
        $stats = is_array($audit['stats'] ?? null) ? $audit['stats'] : [];
        $critical = (int) ($stats['critical_issues'] ?? ($audit['critical'] ?? 0));
        $high = (int) ($stats['high_issues'] ?? 0);
        $medium = (int) ($stats['medium_issues'] ?? 0);
        $low = (int) ($stats['low_issues'] ?? 0);
        $total_issues = (int) ($stats['total_issues'] ?? ($audit['total_issues'] ?? ($critical + $high + $medium + $low)));
        $total_urls = (int) ($stats['total_urls'] ?? ($audit['totalUrls'] ?? 0));
        $score = (int) ($audit['score'] ?? ($audit['overall_score'] ?? 0));
        $audit_id = (string) ($audit['id'] ?? ($audit['audit_id'] ?? ''));
        $completed_at = $audit['completed_at'] ?? ($audit['date'] ?? null);
        $domain = wp_parse_url(home_url('/'), PHP_URL_HOST) ?: '';

        return [
            'id' => $audit_id,
            'audit_id' => $audit_id,
            'domain' => $domain,
            'status' => 'completed',
            'state' => (string) ($audit['state'] ?? 'completed'),
            'audit_type' => 'local',
            'progress' => 100,
            'overallScore' => $score,
            'overall_score' => $score,
            'score' => $score,
            'issues' => [
                'critical' => $critical,
                'high' => $high,
                'medium' => $medium,
                'low' => $low,
                'warning' => $medium + $low,
                'total' => $total_issues,
            ],
            'critical' => $critical,
            'warnings' => $medium + $low,
            'total_issues' => $total_issues,
            'total_urls' => $total_urls,
            'totalUrls' => $total_urls,
            'started_at' => $audit['started_at'] ?? null,
            'completed_at' => $completed_at,
            'created_at' => $completed_at,
            'duration' => (int) ($audit['duration'] ?? 0),
            'stats' => $stats,
        ];
    }

    private function is_local_site_audit_id(string $audit_id): bool {
        return strpos($audit_id, 'audit_') === 0;
    }

    /**
     * Extract comparison payload from non-local audit results.
     */
    private function extract_comparison_data(array $results): ?array {
        $comparison = $results['comparison']
            ?? $results['comparison_data']
            ?? $results['audit_comparison']
            ?? null;

        if (is_array($comparison) && !empty($comparison)) {
            return $comparison;
        }

        if (!empty($results['summary']) && is_array($results['summary'])) {
            $summary_comparison = $results['summary']['comparison']
                ?? $results['summary']['comparison_data']
                ?? null;

            if (is_array($summary_comparison) && !empty($summary_comparison)) {
                return $summary_comparison;
            }
        }

        return null;
    }

    /**
     * Extract URL segmentation payload from non-local audit results.
     */
    private function extract_url_segments(array $results): array {
        $segments = $results['url_segments']
            ?? $results['urlSegments']
            ?? $results['segments']
            ?? null;

        if (is_array($segments)) {
            return $segments;
        }

        if (!empty($results['summary']) && is_array($results['summary'])) {
            $summary_segments = $results['summary']['url_segments']
                ?? $results['summary']['urlSegments']
                ?? null;

            if (is_array($summary_segments)) {
                return $summary_segments;
            }
        }

        return [];
    }

    /**
     * Register routes
     */
    public function register_routes(): void {
        // Audit status (main endpoint)
        register_rest_route($this->namespace, '/audit/status', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [$this, 'get_enhanced_status'],
                'permission_callback' => [$this, 'check_permission'],
            ],
        ]);

        // Enhanced audit status (legacy)
        register_rest_route($this->namespace, '/audit/enhanced-status', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [$this, 'get_enhanced_status'],
                'permission_callback' => [$this, 'check_permission'],
            ],
        ]);

        // Start audit (main endpoint)
        register_rest_route($this->namespace, '/audit/start', [
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [$this, 'start_deep_audit'],
                'permission_callback' => [$this, 'check_permission'],
                'args'                => [
                    'type' => [
                        'required'          => false,
                        'default'           => 'comprehensive',
                        'sanitize_callback' => 'sanitize_text_field',
                    ],
                ],
            ],
        ]);

        // Start deep audit (legacy)
        register_rest_route($this->namespace, '/audit/start-deep', [
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [$this, 'start_deep_audit'],
                'permission_callback' => [$this, 'check_permission'],
                'args'                => [
                    'type' => [
                        'required'          => false,
                        'default'           => 'comprehensive',
                        'sanitize_callback' => 'sanitize_text_field',
                    ],
                ],
            ],
        ]);

        // Test endpoint
        register_rest_route($this->namespace, '/audit/test-enhanced', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => function() {
                    return new \WP_REST_Response(['success' => true, 'message' => 'EnhancedAuditController is working!'], 200);
                },
                'permission_callback' => [$this, 'check_permission'],
            ],
        ]);

        // Quick fixes
        register_rest_route($this->namespace, '/audit/quick-fix', [
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [$this, 'apply_quick_fix'],
                'permission_callback' => [$this, 'check_permission'],
                'args'                => [
                    'fix_id' => [
                        'required'          => true,
                        'sanitize_callback' => 'sanitize_text_field',
                    ],
                    'fix_type' => [
                        'required'          => true,
                        'sanitize_callback' => 'sanitize_text_field',
                    ],
                ],
            ],
        ]);

        // Audit history
        register_rest_route($this->namespace, '/audit/history', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [$this, 'get_audit_history'],
                'permission_callback' => [$this, 'check_permission'],
                'args'                => [
                    'limit' => [
                        'required'          => false,
                        'default'           => 10,
                        'sanitize_callback' => 'absint',
                    ],
                ],
            ],
        ]);

        // Get audit report details
        register_rest_route($this->namespace, '/audit/report/(?P<id>[\w-]+)', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [$this, 'get_audit_report'],
                'permission_callback' => [$this, 'check_permission'],
                'args'                => [
                    'id' => [
                        'required'          => true,
                        'sanitize_callback' => 'sanitize_text_field',
                    ],
                ],
            ],
        ]);
        
        // Stop audit
        register_rest_route($this->namespace, '/audit/stop', [
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [$this, 'stop_audit'],
                'permission_callback' => [$this, 'check_permission'],
            ],
        ]);
        
        // Reset audit (clear all data)
        register_rest_route($this->namespace, '/audit/reset', [
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [$this, 'reset_audit'],
                'permission_callback' => [$this, 'check_permission'],
            ],
        ]);
        
        // Process audit URLs manually
        register_rest_route($this->namespace, '/audit/process', [
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [$this, 'process_audit_urls'],
                'permission_callback' => [$this, 'check_permission'],
            ],
        ]);
        
        // Ping audit service
        register_rest_route($this->namespace, '/audit/ping', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [$this, 'ping_audit_service'],
                'permission_callback' => [$this, 'check_permission'],
            ],
        ]);
        
        // Force complete audit
        register_rest_route($this->namespace, '/audit/force-complete', [
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [$this, 'force_complete_audit'],
                'permission_callback' => [$this, 'check_permission'],
            ],
        ]);
        
        // Test URL discovery
        register_rest_route($this->namespace, '/audit/test-urls', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [$this, 'test_url_discovery'],
                'permission_callback' => [$this, 'check_permission'],
            ],
        ]);
        
        // Get audit issues
        register_rest_route($this->namespace, '/audit/issues', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [$this, 'get_audit_issues'],
                'permission_callback' => [$this, 'check_permission'],
            ],
        ]);
        
        // Fix broken link
        register_rest_route($this->namespace, '/audit/fix-broken-link', [
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [$this, 'fix_broken_link'],
                'permission_callback' => [$this, 'check_permission'],
                'args'                => [
                    'link_id' => [
                        'required'          => true,
                        'sanitize_callback' => 'absint',
                    ],
                    'action' => [
                        'required'          => true,
                        'sanitize_callback' => 'sanitize_text_field',
                        'validate_callback' => function($param) {
                            return in_array($param, ['remove', 'update', 'ignore']);
                        },
                    ],
                    'new_url' => [
                        'required'          => false,
                        'sanitize_callback' => 'esc_url_raw',
                    ],
                ],
            ],
        ]);
        
        // Clear audit data (for development/testing)
        register_rest_route($this->namespace, '/audit/clear', [
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [$this, 'clear_audit_data'],
                'permission_callback' => [$this, 'check_permission'],
            ],
        ]);
        
        // Audit settings
        register_rest_route($this->namespace, '/audit/settings', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [$this, 'get_audit_settings'],
                'permission_callback' => [$this, 'check_permission'],
            ],
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [$this, 'save_audit_settings'],
                'permission_callback' => [$this, 'check_permission'],
                'args'                => [
                    'crawl_speed' => [
                        'required'          => false,
                        'sanitize_callback' => 'absint',
                        'default'           => 10,
                    ],
                    'concurrent_requests' => [
                        'required'          => false,
                        'sanitize_callback' => 'absint',
                        'default'           => 2,
                    ],
                    'request_timeout' => [
                        'required'          => false,
                        'sanitize_callback' => 'absint',
                        'default'           => 3,
                    ],
                    'max_pages' => [
                        'required'          => false,
                        'sanitize_callback' => 'absint',
                        'default'           => 100,
                    ],
                    'max_duration' => [
                        'required'          => false,
                        'sanitize_callback' => 'absint',
                        'default'           => 30,
                    ],
                    'max_depth' => [
                        'required'          => false,
                        'sanitize_callback' => 'absint',
                        'default'           => 5,
                    ],
                ],
            ],
        ]);
    }

    /**
     * Get enhanced audit status
     */
    public function get_enhanced_status(WP_REST_Request $request): WP_REST_Response {
        // Disable ALL caching for this endpoint
        nocache_headers();
        header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
        header('Pragma: no-cache');
        header('Expires: Thu, 01 Jan 1970 00:00:00 GMT');

        try {
            $requested_mode = $this->get_requested_mode($request);
            $local_engine = new SiteAuditEngine();
            $local_status = $this->normalize_local_status($local_engine->get_audit_status());

            if (!empty($local_status['audit_id']) || (($local_status['state'] ?? 'idle') !== 'idle')) {
                $local_status['requested_mode'] = $requested_mode;
                $local_status['resolved_mode'] = 'local';
                $local_status['mode'] = 'local';

                return new WP_REST_Response($local_status, 200);
            }

            return new WP_REST_Response([
                'state' => 'idle',
                'message' => __('No active audit', 'prorank-seo'),
                'can_start' => true,
                'requested_mode' => $requested_mode,
                'resolved_mode' => 'local',
                'mode' => 'local',
            ], 200);
            
        } catch (\Exception $e) {
            return new WP_REST_Response([
                'state' => 'error',
                'message' => __('Failed to load audit status', 'prorank-seo'),
                'error' => $e->getMessage(),
            ], 500);
        }
    }

    /**
     * Start deep audit
     */
    public function start_deep_audit(WP_REST_Request $request): WP_REST_Response {
        try {
            $type = $request->get_param('type');
            if (defined('WP_DEBUG') && WP_DEBUG) {
                prorank_log('[ProRank SEO] Starting audit (type: ' . ($type ?: 'comprehensive') . ')');
            }

            // Settings can be posted either as { settings: { ... } } or directly as the JSON body (UI).
            $settings = $request->get_param('settings');
            if (!is_array($settings)) {
                $settings = $request->get_json_params();
            }
            if (!is_array($settings)) {
                $settings = get_option('prorank_audit_settings', []);
            }

            $local_engine = new SiteAuditEngine();
            $local_status = $local_engine->get_audit_status();
            if (in_array((string) ($local_status['state'] ?? ''), ['running', 'crawling', 'checking', 'analyzing', 'queued', 'pending'], true)) {
                return new WP_REST_Response([
                    'success' => false,
                    'message' => __('A local audit is already running. Please wait for it to complete or stop it first.', 'prorank-seo'),
                    'current_audit' => $this->normalize_local_status($local_status),
                ], 400);
            }

            $local_settings = is_array($settings) ? $settings : [];
            if (!isset($local_settings['max_pages']) && isset($local_settings['max_urls'])) {
                $local_settings['max_pages'] = (int) $local_settings['max_urls'];
            }
            if (!isset($local_settings['max_urls']) && isset($local_settings['max_pages'])) {
                $local_settings['max_urls'] = (int) $local_settings['max_pages'];
            }
            $local_check_types = is_array($local_settings['check_types'] ?? null) ? $local_settings['check_types'] : [];

            $local_args = [
                'type' => $type ?: 'comprehensive',
                'settings' => $local_settings,
                'check_types' => $local_check_types,
                'mode' => 'local',
            ];
            $result = $local_engine->start_audit($local_args);

            return new WP_REST_Response([
                'success' => true,
                'message' => $result['message'] ?? __('Local audit started', 'prorank-seo'),
                'audit_id' => $result['audit_id'] ?? null,
                'status' => $result['status'] ?? 'crawling',
                'mode' => 'local',
            ], 200);
            
        } catch (\Throwable $e) {
            prorank_log('[ProRank SEO] Audit start failed: ' . $e->getMessage());
            if (defined('WP_DEBUG') && WP_DEBUG) {
                prorank_log($e->getTraceAsString());
            }
            
            return new WP_REST_Response([
                'success' => false,
                'message' => __('An error occurred while starting the audit', 'prorank-seo'),
                'error' => 'api_error',
                'details' => [
                    'message' => $e->getMessage(),
                    'file' => basename($e->getFile()),
                    'line' => $e->getLine(),
                ]
            ], 400);
        }
    }

    /**
     * Clear all audit tasks for a specific audit
     */
    private function clear_all_audit_tasks(string $audit_id): void {
        // Not needed with external audit service
    }
    
    /**
     * Apply quick fix
     */
    public function apply_quick_fix(WP_REST_Request $request): WP_REST_Response {
        $fix_id = $request->get_param('fix_id');
        $fix_type = $request->get_param('fix_type');
        
        $result = false;
        $message = '';

        switch ($fix_type) {
            case 'delete_revisions':
                $result = $this->delete_post_revisions();
                $message = __('Post revisions cleaned up', 'prorank-seo');
                break;
                
            case 'optimize_database':
                $result = $this->optimize_database_tables();
                $message = __('Database tables optimized', 'prorank-seo');
                break;
                
            case 'clean_transients':
                $result = $this->clean_expired_transients();
                $message = __('Expired transients removed', 'prorank-seo');
                break;
                
            case 'fix_permalinks':
                flush_rewrite_rules();
                $result = true;
                $message = __('Permalinks refreshed', 'prorank-seo');
                break;
                
        }

        return new WP_REST_Response([
            'success' => $result,
            'message' => $message,
            'fix_id' => $fix_id,
        ], $result ? 200 : 400);
    }

    /**
     * Get audit history
     */
    public function get_audit_history(WP_REST_Request $request): WP_REST_Response {
        try {
            $limit = $request->get_param('limit');

            $limit = is_numeric($limit) ? (int) $limit : 10;
            $limit = max(1, min(50, $limit));
            $local_engine = new SiteAuditEngine();
            $local_history = $local_engine->get_audit_history($limit);
            $audits = array_map([$this, 'normalize_local_history_entry'], is_array($local_history) ? $local_history : []);

            return new WP_REST_Response([
                'audits' => $audits,
                'total' => count($audits),
                'mode' => 'local',
            ], 200);
            
        } catch (\Exception $e) {
            prorank_log('ProRank SEO: Get audit history error: ' . $e->getMessage());
            return new WP_REST_Response([
                'audits' => [],
                'total' => 0,
                'error' => $e->getMessage(),
            ], 200); // Return 200 with empty data to avoid breaking the UI
        }
    }

    /**
     * Get audit report details
     */
    public function get_audit_report(WP_REST_Request $request): WP_REST_Response {
        $report_id = sanitize_text_field((string) $request->get_param('id'));

        if ('' === $report_id) {
            return new WP_REST_Response([
                'success' => false,
                'error' => 'INVALID_AUDIT_ID',
                'message' => __('Audit ID is required', 'prorank-seo'),
            ], 400);
        }

        // Handle local (basic) audit reports.
        if (strpos($report_id, 'local_basic_') === 0) {
            return $this->get_local_audit_report($report_id);
        }

        // Handle local crawler audit reports.
        if ($this->is_local_site_audit_id($report_id)) {
            return $this->get_local_site_audit_report($report_id);
        }

        return new WP_REST_Response([
            'success' => false,
            'error' => 'FREE_PACKAGE_ONLY',
            'message' => __('Only local site audit reports are available in the free plugin.', 'prorank-seo'),
        ], 404);
    }

    /**
     * Build a report response from local (basic) audit data.
     */
    private function get_local_audit_report(string $report_id): WP_REST_Response {
        try {
            $engine = new \ProRank\SEO\Core\Audits\BasicAuditEngine();
            $issues = $engine->get_issues($report_id);

            // Look up the audit row for score / timestamps.
            global $wpdb;
            $audits_table = $wpdb->prefix . 'prorank_audits';
            $row = $wpdb->get_row(
                $wpdb->prepare(
                    "SELECT audit_id, status, started_at, completed_at, total_urls, score
                     FROM %i
                     WHERE audit_id = %s
                     LIMIT 1",
                    $audits_table,
                    $report_id
                )
            );

            if (!$row) {
                return new WP_REST_Response([
                    'success' => false,
                    'error'   => 'AUDIT_NOT_FOUND',
                    'message' => __('Local audit not found', 'prorank-seo'),
                ], 404);
            }

            $score     = (int) ($row->score ?? 0);
            $total_urls = (int) ($row->total_urls ?? 1);
            $domain    = wp_parse_url(home_url('/'), PHP_URL_HOST) ?: '';

            // Group issues into category breakdown.
            $categories = [];
            $severity_totals = ['critical' => 0, 'high' => 0, 'medium' => 0, 'low' => 0, 'warning' => 0];
            $passed = 0;

            foreach ($issues as $issue) {
                $cat_key = $issue['category'] ?? 'other';
                if (!isset($categories[$cat_key])) {
                    $categories[$cat_key] = [
                        'name'         => $this->format_category_name($cat_key),
                        'score'        => 0,
                        'passed'       => 0,
                        'warnings'     => 0,
                        'critical'     => 0,
                        'total_issues' => 0,
                        'checks'       => [],
                    ];
                }

                $sev = $issue['severity'] ?? 'medium';
                $categories[$cat_key]['total_issues']++;

                if ($sev === 'critical' || $sev === 'high') {
                    $categories[$cat_key]['critical']++;
                } elseif ($sev === 'passed') {
                    $categories[$cat_key]['passed']++;
                    $passed++;
                } else {
                    $categories[$cat_key]['warnings']++;
                }

                if (isset($severity_totals[$sev])) {
                    $severity_totals[$sev]++;
                } else {
                    $severity_totals['warning']++;
                }

                // Track unique check types per category.
                $check_type = $issue['title'] ?? $issue['type'] ?? '';
                if ($check_type !== '' && !in_array($check_type, $categories[$cat_key]['checks'], true)) {
                    $categories[$cat_key]['checks'][] = $check_type;
                }
            }

            // Calculate per-category scores.
            foreach ($categories as $key => &$cat) {
                $cat_total = $cat['total_issues'] + $cat['passed'];
                $cat['score'] = $cat_total > 0
                    ? (int) round(($cat['passed'] / $cat_total) * 100)
                    : 0;
            }
            unset($cat);

            $total_issues = count($issues) - $passed;

            $issues_map = [];
            $issues_by_page_map = [];

            foreach ($issues as $issue) {
                if (!is_array($issue)) {
                    continue;
                }

                $normalized_issue = $this->build_local_issue_entry($issue);
                $fingerprint = strtolower(
                    (string) ($normalized_issue['type'] ?? '') . '|' .
                    (string) ($normalized_issue['severity'] ?? '') . '|' .
                    (string) ($normalized_issue['message'] ?? '')
                );

                if (!isset($issues_map[$fingerprint])) {
                    $issues_map[$fingerprint] = $normalized_issue;
                    $issues_map[$fingerprint]['count'] = 0;
                    $issues_map[$fingerprint]['affected_urls'] = [];
                }

                $issues_map[$fingerprint]['count']++;

                $issue_url = $this->normalize_audit_report_url((string) ($normalized_issue['url'] ?? ''));
                if ($issue_url !== '' && !in_array($issue_url, $issues_map[$fingerprint]['affected_urls'], true)) {
                    $issues_map[$fingerprint]['affected_urls'][] = $issue_url;
                }

                if ($issue_url === '') {
                    continue;
                }

                if (!isset($issues_by_page_map[$issue_url])) {
                    $issues_by_page_map[$issue_url] = [];
                }
                if (!isset($issues_by_page_map[$issue_url][$fingerprint])) {
                    $issues_by_page_map[$issue_url][$fingerprint] = $normalized_issue;
                    $issues_by_page_map[$issue_url][$fingerprint]['count'] = 0;
                    $issues_by_page_map[$issue_url][$fingerprint]['affected_urls'] = [$issue_url];
                }
                $issues_by_page_map[$issue_url][$fingerprint]['count']++;
            }

            $normalized_issues = $this->sort_local_issue_entries(array_values($issues_map));
            $issues_by_page = [];
            foreach ($issues_by_page_map as $url => $page_issue_map) {
                $issues_by_page[$url] = $this->sort_local_issue_entries(array_values($page_issue_map));
            }
            ksort($issues_by_page);

            $results = [
                'success'            => true,
                'audit_id'           => $report_id,
                'id'                 => $report_id,
                'domain'             => $domain,
                'overall_score'      => $score,
                'issue_counts'       => [
                    'critical' => $severity_totals['critical'],
                    'high'     => $severity_totals['high'],
                    'medium'   => $severity_totals['medium'],
                    'low'      => $severity_totals['low'],
                    'warning'  => $severity_totals['warning'],
                    'total'    => $total_issues,
                ],
                'category_breakdown' => $categories,
                'statistics'         => [
                    'total_urls'      => $total_urls,
                    'pages_crawled'   => $total_urls,
                    'pages_analyzed'  => $total_urls,
                ],
                'issues'             => $normalized_issues,
                'issues_by_page'     => $issues_by_page,
                'started_at'         => $row->started_at,
                'completed_at'       => $row->completed_at,
                'comparison'         => null,
                'comparison_data'    => null,
                'url_segments'       => [],
                'urlSegments'        => [],
            ];

            return new WP_REST_Response($results, 200);
        } catch (\Throwable $e) {
            prorank_log('ProRank SEO: Local audit report error: ' . $e->getMessage());
            return new WP_REST_Response([
                'success' => false,
                'error'   => 'INTERNAL_ERROR',
                'message' => __('An error occurred while loading the local audit report', 'prorank-seo'),
            ], 500);
        }
    }

    /**
     * Build a report response from local crawler (SiteAuditEngine) data.
     */
    private function get_local_site_audit_report(string $report_id): WP_REST_Response {
        try {
            $engine = new SiteAuditEngine();
            $status = $this->normalize_local_status($engine->get_audit_status($report_id));

            if (empty($status['audit_id'])) {
                return new WP_REST_Response([
                    'success' => false,
                    'error'   => 'AUDIT_NOT_FOUND',
                    'message' => __('Local audit not found', 'prorank-seo'),
                ], 404);
            }

            $stats = is_array($status['stats'] ?? null) ? $status['stats'] : [];
            $critical = (int) ($stats['critical_issues'] ?? 0);
            $high = (int) ($stats['high_issues'] ?? 0);
            $medium = (int) ($stats['medium_issues'] ?? 0);
            $low = (int) ($stats['low_issues'] ?? 0);
            $warning = (int) ($stats['warning_issues'] ?? ($medium + $low));
            $total_issues = (int) ($stats['total_issues'] ?? ($critical + $high + $medium + $low));
            $total_urls = (int) ($stats['total_urls'] ?? 0);
            $score = (int) ($status['score'] ?? 0);
            $domain = wp_parse_url(home_url('/'), PHP_URL_HOST) ?: '';

            $category_breakdown = [];
            $raw_categories = is_array($status['categories'] ?? null) ? $status['categories'] : [];
            foreach ($raw_categories as $key => $cat_data) {
                $cat_total = (int) ($cat_data['total'] ?? 0);
                $cat_passed = (int) ($cat_data['passed'] ?? 0);
                $cat_warnings = (int) ($cat_data['warnings'] ?? 0);
                $cat_critical = (int) ($cat_data['critical'] ?? 0);
                $cat_total_issues = max(0, $cat_critical + $cat_warnings);

                $category_breakdown[$key] = [
                    'name' => (string) ($cat_data['name'] ?? $this->format_category_name(str_replace('-', '_', (string) $key))),
                    'score' => (int) ($cat_data['score'] ?? ($cat_total > 0 ? round(($cat_passed / $cat_total) * 100) : 100)),
                    'passed' => $cat_passed,
                    'warnings' => $cat_warnings,
                    'critical' => $cat_critical,
                    'total_issues' => $cat_total_issues,
                    'checks' => is_array($cat_data['checks'] ?? null) ? $cat_data['checks'] : [],
                ];
            }

            global $wpdb;
            $audit_urls_table = $wpdb->prefix . 'prorank_audit_urls';
            $audit_issues_table = $wpdb->prefix . 'prorank_audit_issues';
            $issue_rows = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT u.url, i.type, i.severity, i.message, i.data
                     FROM %i i
                     JOIN %i u ON u.id = i.url_id
                     WHERE i.audit_id = %s AND i.severity != 'passed'
                     ORDER BY u.url ASC, FIELD(i.severity, 'critical', 'high', 'medium', 'low')",
                    $audit_issues_table,
                    $audit_urls_table,
                    $report_id
                )
            );

            $issues_map = [];
            $issues_by_page_map = [];

            if (!empty($issue_rows)) {
                foreach ($issue_rows as $row) {
                    $row_data = [];
                    if (!empty($row->data)) {
                        $decoded = json_decode((string) $row->data, true);
                        if (is_array($decoded)) {
                            $row_data = $decoded;
                        }
                    }

                    $issue_url = $this->normalize_audit_report_url((string) ($row->url ?? ''));
                    $normalized_issue = $this->build_local_issue_entry([
                        'type' => (string) ($row->type ?? ''),
                        'severity' => (string) ($row->severity ?? 'medium'),
                        'message' => (string) ($row->message ?? ''),
                        'url' => $issue_url,
                        'data' => $row_data,
                    ]);

                    $fingerprint = strtolower(
                        (string) ($normalized_issue['type'] ?? '') . '|' .
                        (string) ($normalized_issue['severity'] ?? '') . '|' .
                        (string) ($normalized_issue['message'] ?? '')
                    );

                    if (!isset($issues_map[$fingerprint])) {
                        $issues_map[$fingerprint] = $normalized_issue;
                        $issues_map[$fingerprint]['count'] = 0;
                        $issues_map[$fingerprint]['affected_urls'] = [];
                    }
                    $issues_map[$fingerprint]['count']++;

                    if ($issue_url !== '' && !in_array($issue_url, $issues_map[$fingerprint]['affected_urls'], true)) {
                        $issues_map[$fingerprint]['affected_urls'][] = $issue_url;
                    }

                    if ($issue_url === '') {
                        continue;
                    }

                    if (!isset($issues_by_page_map[$issue_url])) {
                        $issues_by_page_map[$issue_url] = [];
                    }
                    if (!isset($issues_by_page_map[$issue_url][$fingerprint])) {
                        $issues_by_page_map[$issue_url][$fingerprint] = $normalized_issue;
                        $issues_by_page_map[$issue_url][$fingerprint]['count'] = 0;
                        $issues_by_page_map[$issue_url][$fingerprint]['affected_urls'] = [$issue_url];
                    }
                    $issues_by_page_map[$issue_url][$fingerprint]['count']++;
                }
            }

            if (empty($issues_map) && is_array($status['issues'] ?? null)) {
                foreach ($status['issues'] as $issue) {
                    if (!is_array($issue)) {
                        continue;
                    }

                    $normalized_issue = $this->build_local_issue_entry($issue);
                    $fingerprint = strtolower(
                        (string) ($normalized_issue['type'] ?? '') . '|' .
                        (string) ($normalized_issue['severity'] ?? '') . '|' .
                        (string) ($normalized_issue['message'] ?? '')
                    );

                    if (!isset($issues_map[$fingerprint])) {
                        $issues_map[$fingerprint] = $normalized_issue;
                        $issues_map[$fingerprint]['count'] = 0;
                        $issues_map[$fingerprint]['affected_urls'] = [];
                    }

                    $item_count = (int) ($issue['count'] ?? 0);
                    if ($item_count <= 0) {
                        $item_count = (int) ($normalized_issue['count'] ?? 1);
                    }
                    $issues_map[$fingerprint]['count'] += max(1, $item_count);

                    $issue_urls = is_array($normalized_issue['affected_urls'] ?? null)
                        ? $normalized_issue['affected_urls']
                        : [];
                    foreach ($issue_urls as $issue_url) {
                        if (!in_array($issue_url, $issues_map[$fingerprint]['affected_urls'], true)) {
                            $issues_map[$fingerprint]['affected_urls'][] = $issue_url;
                        }
                    }
                }
            }

            $normalized_issues = $this->sort_local_issue_entries(array_values($issues_map));
            $issues_by_page = [];
            foreach ($issues_by_page_map as $url => $page_issue_map) {
                $issues_by_page[$url] = $this->sort_local_issue_entries(array_values($page_issue_map));
            }
            ksort($issues_by_page);

            $results = [
                'success'            => true,
                'audit_id'           => $report_id,
                'id'                 => $report_id,
                'domain'             => $domain,
                'score'              => $score,
                'overall_score'      => $score,
                'overallScore'       => $score,
                'issue_counts'       => [
                    'critical' => $critical,
                    'high'     => $high,
                    'medium'   => $medium,
                    'low'      => $low,
                    'warning'  => $warning,
                    'total'    => $total_issues,
                ],
                'category_breakdown' => $category_breakdown,
                'statistics'         => [
                    'total_urls'      => $total_urls,
                    'pages_crawled'   => $total_urls,
                    'pages_analyzed'  => $total_urls,
                ],
                'issues'             => $normalized_issues,
                'issues_by_page'     => $issues_by_page,
                'started_at'         => $status['started_at'] ?? null,
                'completed_at'       => $status['completed_at'] ?? null,
                'comparison'         => null,
                'comparison_data'    => null,
                'url_segments'       => [],
                'urlSegments'        => [],
            ];

            return new WP_REST_Response($results, 200);
        } catch (\Throwable $e) {
            prorank_log('ProRank SEO: Local crawler audit report error: ' . $e->getMessage());
            return new WP_REST_Response([
                'success' => false,
                'error'   => 'INTERNAL_ERROR',
                'message' => __('An error occurred while loading the local audit report', 'prorank-seo'),
            ], 500);
        }
    }

    /**
     * Format a category key into a human-readable name.
     */
    private function format_category_name(string $key): string {
        $map = [
            'technical_seo' => 'Technical SEO',
            'on_page_seo'   => 'On-Page SEO',
            'performance'   => 'Performance',
            'security'      => 'Security',
            'content'       => 'Content',
        ];

        return $map[$key] ?? ucwords(str_replace('_', ' ', $key));
    }

    /**
     * Sort issue entries by severity, then by finding count.
     */
    private function sort_local_issue_entries(array $entries): array {
        $severity_order = [
            'critical' => 0,
            'high' => 1,
            'medium' => 2,
            'low' => 3,
            'warning' => 4,
        ];

        usort($entries, static function (array $a, array $b) use ($severity_order): int {
            $a_severity = strtolower((string) ($a['severity'] ?? 'warning'));
            $b_severity = strtolower((string) ($b['severity'] ?? 'warning'));
            $a_rank = $severity_order[$a_severity] ?? 5;
            $b_rank = $severity_order[$b_severity] ?? 5;

            if ($a_rank !== $b_rank) {
                return $a_rank <=> $b_rank;
            }

            $a_count = (int) ($a['count'] ?? 0);
            $b_count = (int) ($b['count'] ?? 0);
            if ($a_count !== $b_count) {
                return $b_count <=> $a_count;
            }

            return strcmp((string) ($a['title'] ?? ''), (string) ($b['title'] ?? ''));
        });

        return $entries;
    }

    /**
     * Build a normalized local issue entry for report rendering.
     */
    private function build_local_issue_entry(array $raw_issue): array {
        $issue_type = sanitize_key((string) ($raw_issue['type'] ?? $raw_issue['issue_type'] ?? 'unknown_issue'));
        if ($issue_type === '') {
            $issue_type = 'unknown_issue';
        }

        $severity = strtolower((string) ($raw_issue['severity'] ?? 'medium'));
        $allowed_severities = ['critical', 'high', 'medium', 'low', 'warning'];
        if (!in_array($severity, $allowed_severities, true)) {
            $severity = 'medium';
        }

        $metadata = $this->get_local_issue_metadata($issue_type);
        $fallback_category = (string) ($metadata['category'] ?? $this->infer_local_issue_category($issue_type));

        $raw_category = (string) ($raw_issue['category'] ?? $raw_issue['issue_category'] ?? $fallback_category);
        $category = strtolower(str_replace('-', '_', trim($raw_category)));
        if ($category === '') {
            $category = $fallback_category;
        }

        $message = trim((string) ($raw_issue['message'] ?? $raw_issue['description'] ?? ''));

        $data = [];
        if (is_array($raw_issue['data'] ?? null)) {
            $data = $raw_issue['data'];
        }

        $title = trim((string) ($raw_issue['title'] ?? ''));
        if ($title === '') {
            $title = (string) ($metadata['title'] ?? '');
        }
        if ($title === '') {
            $title = $this->format_issue_type_label($issue_type);
        }

        $description = trim((string) ($raw_issue['description'] ?? ''));
        if ($description === '') {
            $description = $message !== '' ? $message : $title;
        }

        $fix_from_data = '';
        if (is_string($data['fix'] ?? null)) {
            $fix_from_data = trim((string) $data['fix']);
        }

        $how_to_fix = trim((string) ($raw_issue['how_to_fix'] ?? $raw_issue['fix_suggestion'] ?? ($data['how_to_fix'] ?? $fix_from_data)));
        if ($how_to_fix === '') {
            $how_to_fix = (string) ($metadata['how_to_fix'] ?? '');
        }

        $affected_urls = [];
        if (is_array($raw_issue['affected_urls'] ?? null)) {
            foreach ($raw_issue['affected_urls'] as $affected_url) {
                if (!is_string($affected_url)) {
                    continue;
                }
                $normalized_affected_url = $this->normalize_audit_report_url($affected_url);
                if ($normalized_affected_url !== '' && !in_array($normalized_affected_url, $affected_urls, true)) {
                    $affected_urls[] = $normalized_affected_url;
                }
            }
        }

        $issue_url = $this->normalize_audit_report_url((string) ($raw_issue['url'] ?? ''));
        if ($issue_url !== '' && !in_array($issue_url, $affected_urls, true)) {
            $affected_urls[] = $issue_url;
        }

        $count = (int) ($raw_issue['count'] ?? 0);
        if ($count <= 0) {
            $count = max(1, count($affected_urls));
        }

        if ($issue_url === '') {
            $issue_url = $affected_urls[0] ?? home_url('/');
        }

        $module_path = $this->get_module_path_for_issue_category($category);
        $display_category = (string) ($raw_issue['display_category'] ?? $this->format_category_name($category));

        $id = (string) ($raw_issue['id'] ?? '');
        if ($id === '') {
            $id = $issue_type . '_' . md5($message . '|' . $severity . '|' . $category);
        }

        return [
            'id' => $id,
            'type' => $issue_type,
            'issue_type' => $issue_type,
            'check_type' => $issue_type,
            'title' => $title,
            'description' => $description,
            'message' => $message !== '' ? $message : $description,
            'severity' => $severity,
            'category' => $category,
            'display_category' => $display_category,
            'how_to_fix' => $how_to_fix,
            'fix_suggestion' => $how_to_fix,
            'url' => $issue_url,
            'affected_urls' => $affected_urls,
            'count' => $count,
            'module_path' => $module_path,
            'module_url' => admin_url($module_path),
        ];
    }

    /**
     * Get metadata for known local issue types.
     */
    private function get_local_issue_metadata(string $issue_type): array {
        $definitions = [
            'missing_alt_text' => [
                'title' => __('Missing image alt text', 'prorank-seo'),
                'category' => 'on_page_seo',
                'how_to_fix' => __('Add descriptive alt text to every content image in Media Library or editor image blocks.', 'prorank-seo'),
            ],
            'short_title' => [
                'title' => __('Title is too short', 'prorank-seo'),
                'category' => 'on_page_seo',
                'how_to_fix' => __('Expand the SEO title to roughly 30-60 characters while keeping the primary topic near the beginning.', 'prorank-seo'),
            ],
            'long_title' => [
                'title' => __('Title may be truncated', 'prorank-seo'),
                'category' => 'on_page_seo',
                'how_to_fix' => __('Shorten the SEO title to roughly 50-60 characters to reduce truncation risk in SERPs.', 'prorank-seo'),
            ],
            'short_meta_description' => [
                'title' => __('Meta description is too short', 'prorank-seo'),
                'category' => 'on_page_seo',
                'how_to_fix' => __('Rewrite the meta description to around 140-160 characters with a clear value proposition and CTA.', 'prorank-seo'),
            ],
            'long_meta_description' => [
                'title' => __('Meta description is too long', 'prorank-seo'),
                'category' => 'on_page_seo',
                'how_to_fix' => __('Trim the meta description to around 140-160 characters so it is less likely to be cut off.', 'prorank-seo'),
            ],
            'missing_meta_description' => [
                'title' => __('Missing meta description', 'prorank-seo'),
                'category' => 'on_page_seo',
                'how_to_fix' => __('Add a unique meta description for this page in ProRank meta settings.', 'prorank-seo'),
            ],
            'missing_og_tags' => [
                'title' => __('Missing Open Graph tags', 'prorank-seo'),
                'category' => 'on_page_seo',
                'how_to_fix' => __('Enable Social metadata in ProRank and ensure og:title, og:description, and og:image are output.', 'prorank-seo'),
            ],
            'missing_twitter_cards' => [
                'title' => __('Missing Twitter Card tags', 'prorank-seo'),
                'category' => 'on_page_seo',
                'how_to_fix' => __('Enable Twitter Card metadata in ProRank social settings.', 'prorank-seo'),
            ],
            'missing_h1' => [
                'title' => __('Missing H1 heading', 'prorank-seo'),
                'category' => 'content',
                'how_to_fix' => __('Add one clear H1 heading that describes the page topic and avoid using multiple H1s.', 'prorank-seo'),
            ],
            'multiple_h1' => [
                'title' => __('Multiple H1 headings found', 'prorank-seo'),
                'category' => 'content',
                'how_to_fix' => __('Keep one primary H1 and convert additional H1s to H2/H3 subheadings.', 'prorank-seo'),
            ],
            'orphaned_page' => [
                'title' => __('Orphaned page', 'prorank-seo'),
                'category' => 'technical_seo',
                'how_to_fix' => __('Add at least one contextual internal link from an indexed page to this URL.', 'prorank-seo'),
            ],
            'broken_link' => [
                'title' => __('Broken internal link', 'prorank-seo'),
                'category' => 'technical_seo',
                'how_to_fix' => __('Update or remove links that return 4xx/5xx responses, then rescan.', 'prorank-seo'),
            ],
            'broken_internal_links' => [
                'title' => __('Broken internal links found', 'prorank-seo'),
                'category' => 'technical_seo',
                'how_to_fix' => __('Fix each broken internal URL by correcting destination slugs, redirects, or removing dead links.', 'prorank-seo'),
            ],
            'http_error' => [
                'title' => __('Page returned an HTTP error', 'prorank-seo'),
                'category' => 'technical_seo',
                'how_to_fix' => __('Resolve the failing URL response (404/500), add redirects if needed, and ensure the page is indexable.', 'prorank-seo'),
            ],
            'broken_image' => [
                'title' => __('Broken image detected', 'prorank-seo'),
                'category' => 'performance',
                'how_to_fix' => __('Replace or re-upload missing images and update stale image URLs in content.', 'prorank-seo'),
            ],
            'broken_images' => [
                'title' => __('Broken images detected', 'prorank-seo'),
                'category' => 'performance',
                'how_to_fix' => __('Replace or re-upload missing images and update stale image URLs in content.', 'prorank-seo'),
            ],
            'core_web_vitals_lcp' => [
                'title' => __('Largest Contentful Paint needs improvement', 'prorank-seo'),
                'category' => 'performance',
                'how_to_fix' => __('Optimize above-the-fold images, reduce render-blocking CSS/JS, and improve server response time.', 'prorank-seo'),
            ],
            'no_cache_headers' => [
                'title' => __('Cache headers are missing', 'prorank-seo'),
                'category' => 'performance',
                'how_to_fix' => __('Enable browser caching in ProRank Performance and verify Cache-Control/Expires headers are sent.', 'prorank-seo'),
            ],
            'no_compression' => [
                'title' => __('Response compression is not enabled', 'prorank-seo'),
                'category' => 'performance',
                'how_to_fix' => __('Enable Brotli or GZIP compression at the server or CDN layer.', 'prorank-seo'),
            ],
            'slow_load_time' => [
                'title' => __('Page load time is slow', 'prorank-seo'),
                'category' => 'performance',
                'how_to_fix' => __('Reduce render-blocking assets, optimize images/fonts, and enable page caching.', 'prorank-seo'),
            ],
            'plugins_need_updates' => [
                'title' => __('Plugins need updates', 'prorank-seo'),
                'category' => 'security',
                'how_to_fix' => __('Update outdated plugins to patch security issues and keep compatibility stable.', 'prorank-seo'),
            ],
        ];

        if (isset($definitions[$issue_type])) {
            return $definitions[$issue_type];
        }

        return [
            'title' => $this->format_issue_type_label($issue_type),
            'category' => $this->infer_local_issue_category($issue_type),
            'how_to_fix' => __('Open the relevant module, apply the recommended change, and rerun the audit to verify the fix.', 'prorank-seo'),
        ];
    }

    /**
     * Infer category for unknown issue types.
     */
    private function infer_local_issue_category(string $issue_type): string {
        $technical_keywords = ['canonical', 'redirect', 'sitemap', 'robots', 'orphan', 'http_error', 'broken_link', 'crawl', 'index'];
        $on_page_keywords = ['title', 'meta', 'og', 'twitter', 'alt', 'schema', 'h1', 'heading'];
        $performance_keywords = ['lcp', 'core_web_vitals', 'speed', 'cache', 'compress', 'image', 'css', 'js', 'font', 'load_time'];
        $security_keywords = ['https', 'ssl', 'mixed_content', 'security', 'plugin', 'vulnerab', 'xss'];

        foreach ($performance_keywords as $keyword) {
            if (strpos($issue_type, $keyword) !== false) {
                return 'performance';
            }
        }
        foreach ($security_keywords as $keyword) {
            if (strpos($issue_type, $keyword) !== false) {
                return 'security';
            }
        }
        foreach ($on_page_keywords as $keyword) {
            if (strpos($issue_type, $keyword) !== false) {
                return 'on_page_seo';
            }
        }
        foreach ($technical_keywords as $keyword) {
            if (strpos($issue_type, $keyword) !== false) {
                return 'technical_seo';
            }
        }

        return 'content';
    }

    /**
     * Resolve module page for issue category.
     */
    private function get_module_path_for_issue_category(string $category): string {
        $normalized = strtolower(str_replace('-', '_', $category));
        $map = [
            'technical_seo' => 'admin.php?page=prorank-technical-seo',
            'security' => 'admin.php?page=prorank-technical-seo',
            'on_page_seo' => 'admin.php?page=prorank-on-page-seo',
            'content' => 'admin.php?page=prorank-on-page-seo',
            'performance' => 'admin.php?page=prorank-performance',
        ];

        return $map[$normalized] ?? 'admin.php?page=prorank-site-audit';
    }

    /**
     * Normalize URL for report grouping.
     */
    private function normalize_audit_report_url(string $url): string {
        $url = trim($url);
        if ($url === '') {
            return '';
        }

        $parts = wp_parse_url($url);
        if (!is_array($parts)) {
            return $url;
        }

        $home_parts = wp_parse_url(home_url('/'));
        $scheme = (string) ($home_parts['scheme'] ?? ($parts['scheme'] ?? 'https'));
        $host = (string) ($home_parts['host'] ?? ($parts['host'] ?? ''));
        $path = '/' . ltrim((string) ($parts['path'] ?? '/'), '/');
        if ($path !== '/' && str_ends_with($path, '/')) {
            $path = rtrim($path, '/');
        }
        $query = isset($parts['query']) && $parts['query'] !== '' ? '?' . $parts['query'] : '';

        if ($host === '') {
            return $path . $query;
        }

        return $scheme . '://' . $host . $path . $query;
    }

    /**
     * Humanize issue type keys for UI labels.
     */
    private function format_issue_type_label(string $issue_type): string {
        $label = trim(str_replace(['_', '-'], ' ', $issue_type));
        if ($label === '') {
            return __('Issue', 'prorank-seo');
        }

        return ucwords($label);
    }
    
    /**
     * Stop audit
     */
    public function stop_audit(WP_REST_Request $request): WP_REST_Response {
        try {
            $local_engine = new SiteAuditEngine();
            $stopped = $local_engine->stop_audit();

            if ($stopped) {
                return new WP_REST_Response([
                    'success' => true,
                    'message' => __('Local audit stopped successfully', 'prorank-seo'),
                    'mode' => 'local',
                ], 200);
            }

            return new WP_REST_Response([
                'success' => false,
                'message' => __('No running local audit to stop', 'prorank-seo'),
                'mode' => 'local',
            ], 400);
        } catch (\Exception $e) {
            prorank_log('ProRank SEO: Stop audit error: ' . $e->getMessage());
            return new WP_REST_Response([
                'success' => false,
                'message' => __('Failed to stop audit', 'prorank-seo'),
                'error' => $e->getMessage(),
            ], 500);
        }
    }
    
    /**
     * Get audit issues
     */
    public function get_audit_issues(WP_REST_Request $request): WP_REST_Response {
        try {
            $issues = [];
            $local_engine = new SiteAuditEngine();
            $local_status = $local_engine->get_audit_status();
            if (!empty($local_status['audit_id'])) {
                $issues = is_array($local_status['issues'] ?? null) ? $local_status['issues'] : [];
            }
            
            return new WP_REST_Response([
                'issues' => $issues,
                'mode' => 'local',
            ], 200);
            
        } catch (\Exception $e) {
            return new WP_REST_Response([
                'issues' => [],
                'error' => $e->getMessage(),
            ], 500);
        }
    }
    public function fix_broken_link(WP_REST_Request $request): WP_REST_Response {
        $link_id = $request->get_param('link_id');
        $action = $request->get_param('action');
        $new_url = $request->get_param('new_url');
        
        global $wpdb;
        
        // Get link details
        $link = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$this->broken_links_table} WHERE id = %d",
            $link_id
        ));
        
        if (!$link) {
            return new WP_REST_Response([
                'success' => false,
                'message' => __('Link not found', 'prorank-seo'),
            ], 404);
        }
        
        $success = false;
        $message = '';
        
        switch ($action) {
            case 'remove':
                $success = $this->remove_link_from_content($link);
                $message = __('Link removed from content', 'prorank-seo');
                break;
                
            case 'update':
                if ($new_url) {
                    $success = $this->update_link_in_content($link, $new_url);
                    $message = __('Link updated successfully', 'prorank-seo');
                }
                break;
                
            case 'ignore':
                $success = $wpdb->update(
                    $this->broken_links_table,
                    ['status' => 'ignored'],
                    ['id' => $link_id]
                );
                $message = __('Link marked as ignored', 'prorank-seo');
                break;
        }
        
        return new WP_REST_Response([
            'success' => (bool) $success,
            'message' => $message,
        ], $success ? 200 : 400);
    }

    /**
     * Get safe quick fixes without database calls
     */
    private function get_safe_quick_fixes(): array {
        return [
            [
                'id' => 'optimize_images',
                'type' => 'optimize_images',
                'title' => __('Optimize large images', 'prorank-seo'),
                'description' => __('Compress and resize large images to improve page load speed', 'prorank-seo'),
                'severity' => 'warning',
                'impact' => __('Can reduce page load time by 20-30%', 'prorank-seo'),
            ],
            [
                'id' => 'enable_caching',
                'type' => 'enable_caching',
                'title' => __('Enable browser caching', 'prorank-seo'),
                'description' => __('Set proper cache headers for static resources', 'prorank-seo'),
                'severity' => 'warning',
                'impact' => __('Reduces server load and improves repeat visit speed', 'prorank-seo'),
            ],
            [
                'id' => 'fix_mixed_content',
                'type' => 'fix_mixed_content',
                'title' => __('Fix mixed content issues', 'prorank-seo'),
                'description' => __('Update HTTP resources to HTTPS for better security', 'prorank-seo'),
                'severity' => 'critical',
                'impact' => __('Improves security and SEO rankings', 'prorank-seo'),
            ],
        ];
    }

    /**
     * Get available quick fixes
     */
    private function get_available_quick_fixes(): array {
        $fixes = [];

        // Check for post revisions
        $revision_count = $this->count_post_revisions();
        if ($revision_count > 100) {
            $fixes[] = [
                'id' => 'delete_revisions',
                'type' => 'delete_revisions',
                'title' => __('Clean up post revisions', 'prorank-seo'),
                'description' => sprintf(
/* translators: %1$: value */
                    __('Remove %1$d old post revisions to free up database space', 'prorank-seo'),
                    $revision_count
                ),
                'severity' => 'warning',
                'impact' => __('Improves database performance', 'prorank-seo'),
            ];
        }

        // Check for expired transients
        $transient_count = $this->count_expired_transients();
        if ($transient_count > 50) {
            $fixes[] = [
                'id' => 'clean_transients',
                'type' => 'clean_transients',
                'title' => __('Clean expired transients', 'prorank-seo'),
                'description' => sprintf(
/* translators: %1$: value */
                    __('Remove %1$d expired transients from the database', 'prorank-seo'),
                    $transient_count
                ),
                'severity' => 'warning',
                'impact' => __('Reduces database size', 'prorank-seo'),
            ];
        }

        // Check if robots.txt needs optimization (integrates with existing robots editor)
        $plugin = \ProRank\SEO\Plugin::get_instance();
        $robots_module = null;
        if ($plugin) {
            $module_manager = $plugin->modules();
            if ($module_manager) {
                $robots_module = $module_manager->get_module('robots-indexing');
            }
        }
        if ($robots_module && $robots_module->is_active()) {
            // Let the existing module handle robots.txt
        }

        return $fixes;
    }

    /**
     * Get all audit issues
     */
    private function get_all_issues(): array {
        $issues = [];
        
        // Check for broken links
        $broken_links = $this->check_broken_links();
        if (!empty($broken_links)) {
            $issues[] = [
                'id' => 'broken_links',
                'category' => 'content',
                'severity' => 'critical',
                'title' => __('Broken links found', 'prorank-seo'),
/* translators: %1$: value */
                'description' => sprintf(__('%1$d broken links detected', 'prorank-seo'), count($broken_links)),
                'affected_urls' => array_unique(array_column($broken_links, 'post_url')),
                'details' => $broken_links,
                'expandable' => true,
            ];
        }
        
        // Check for missing alt text
        $missing_alt = $this->check_missing_alt_text();
        if ($missing_alt > 0) {
            $issues[] = [
                'id' => 'missing_alt_text',
                'category' => 'content',
                'severity' => 'warning',
                'title' => __('Images missing alt text', 'prorank-seo'),
/* translators: %1$: value */
                'description' => sprintf(__('%1$d images found without alt text', 'prorank-seo'), $missing_alt),
                'affected_urls' => ['/about', '/services', '/contact'],
            ];
        }
        
        // Check page speed (example)
        $issues[] = [
            'id' => 'slow_page_speed',
            'category' => 'performance',
            'severity' => 'critical',
            'title' => __('Slow page load times', 'prorank-seo'),
            'description' => __('Homepage takes 4.5s to load (should be under 3s)', 'prorank-seo'),
            'affected_urls' => ['/'],
        ];
        
        return $issues;
    }

    /**
     * Helper: Count post revisions
     */
    private function count_post_revisions(): int {
        global $wpdb;
        return (int) $wpdb->get_var(
            "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = 'revision'"
        );
    }

    /**
     * Helper: Delete post revisions
     */
    private function delete_post_revisions(): bool {
        global $wpdb;
        $wpdb->query("DELETE FROM {$wpdb->posts} WHERE post_type = 'revision'");
        return true;
    }

    /**
     * Helper: Count expired transients
     */
    private function count_expired_transients(): int {
        global $wpdb;
        $time = time();
        return (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$wpdb->options} 
             WHERE option_name LIKE %s 
             AND option_value < %s",
            '_transient_timeout_%',
            $time
        ));
    }

    /**
     * Helper: Clean expired transients
     */
    private function clean_expired_transients(): bool {
        global $wpdb;
        $time = time();
        
        $wpdb->query($wpdb->prepare(
            "DELETE a, b FROM {$wpdb->options} a, {$wpdb->options} b
             WHERE a.option_name LIKE %s
             AND a.option_value < %s
             AND b.option_name = CONCAT('_transient_', SUBSTRING(a.option_name, 12))
             AND b.option_name LIKE %s",
            '_transient_timeout_%',
            $time,
            '_transient_%'
        ));
        
        return true;
    }

    /**
     * Helper: Optimize database tables
     */
    private function optimize_database_tables(): bool {
        global $wpdb;
        
        $tables = $wpdb->get_col("SHOW TABLES");
        foreach ($tables as $table) {
            $wpdb->query("OPTIMIZE TABLE `{$table}`");
        }
        
        return true;
    }
    
    /**
     * Check for broken links
     */
    private function check_broken_links(): array {
        global $wpdb;
        
        // Create table if it doesn't exist
        $this->maybe_create_broken_links_table();
        
        // Get broken links from database
        $broken_links = $wpdb->get_results(
            "SELECT bl.*, p.post_title, p.guid as post_url 
             FROM {$this->broken_links_table} bl
             LEFT JOIN {$wpdb->posts} p ON bl.post_id = p.ID
             WHERE bl.status IN ('broken', '404', 'timeout')
             ORDER BY bl.created_at DESC
             LIMIT 100",
            ARRAY_A
        );
        
        return $broken_links ?: [];
    }
    
    /**
     * Check for missing alt text
     */
    private function check_missing_alt_text(): int {
        global $wpdb;
        
        return (int) $wpdb->get_var(
            "SELECT COUNT(DISTINCT p.ID)
             FROM {$wpdb->posts} p
             LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_wp_attachment_image_alt'
             WHERE p.post_type = 'attachment'
             AND p.post_mime_type LIKE 'image/%'
             AND (pm.meta_value IS NULL OR pm.meta_value = '')"
        );
    }
    
    /**
     * Create broken links table if needed
     */
    private function maybe_create_broken_links_table(): void {
        TableRegistry::ensure_tables(true);
    }
    
    /**
     * Remove link from content
     */
    private function remove_link_from_content($link): bool {
        $post = get_post($link->post_id);
        if (!$post) {
            return false;
        }
        
        // Create pattern to find the link
        $pattern = '/<a[^>]*href=["\']' . preg_quote($link->url, '/') . '["\'][^>]*>.*?<\/a>/i';
        
        // Remove the link but keep the text
        $new_content = preg_replace_callback($pattern, function($matches) {
            return wp_strip_all_tags($matches[0]);
        }, $post->post_content);
        
        if ($new_content !== $post->post_content) {
            wp_update_post([
                'ID' => $post->ID,
                'post_content' => $new_content,
            ]);
            
            // Remove from broken links table
            global $wpdb;
            $wpdb->delete($this->broken_links_table, ['id' => $link->id]);
            
            return true;
        }
        
        return false;
    }
    
    /**
     * Update link in content
     */
    private function update_link_in_content($link, string $new_url): bool {
        $post = get_post($link->post_id);
        if (!$post) {
            return false;
        }
        
        // Replace old URL with new URL
        $new_content = str_replace(
            'href="' . $link->url . '"',
            'href="' . $new_url . '"',
            $post->post_content
        );
        
        $new_content = str_replace(
            "href='" . $link->url . "'",
            "href='" . $new_url . "'",
            $new_content
        );
        
        if ($new_content !== $post->post_content) {
            wp_update_post([
                'ID' => $post->ID,
                'post_content' => $new_content,
            ]);
            
            // Update link status
            global $wpdb;
            $wpdb->update(
                $this->broken_links_table,
                [
                    'url' => $new_url,
                    'status' => 'fixed',
                    'status_message' => 'Updated by user',
                ],
                ['id' => $link->id]
            );
            
            return true;
        }
        
        return false;
    }
    
    /**
     * Clear audit data
     */
    public function clear_audit_data(WP_REST_Request $request): WP_REST_Response {
        try {
            $audit_engine = new SiteAuditEngine();
            $result = $audit_engine->clear_audit_data();
            
            if ($result) {
                return new WP_REST_Response([
                    'success' => true,
                    'message' => __('Audit data cleared successfully', 'prorank-seo'),
                ], 200);
            }

            return new WP_REST_Response([
                'success' => false,
                'message' => __('Failed to clear audit data', 'prorank-seo'),
            ], 500);
        } catch (\Exception $e) {
            return new WP_REST_Response([
                'success' => false,
                'message' => __('Error clearing audit data', 'prorank-seo'),
                'error' => $e->getMessage(),
            ], 500);
        }
    }
    
    /**
     * Force complete audit
     */
    public function force_complete_audit(WP_REST_Request $request): WP_REST_Response {
        return new WP_REST_Response([
            'success' => false,
            'message' => __('Force-complete is not available for the local free audit runner.', 'prorank-seo'),
        ], 400);
    }
    
    /**
     * Process audit URLs manually
     */
    public function process_audit_urls(WP_REST_Request $request): WP_REST_Response {
        return new WP_REST_Response([
            'success' => true,
            'message' => __('The local audit runner processes queued URLs automatically.', 'prorank-seo'),
        ], 200);
    }

    /**
     * Ping audit service
     */
    public function ping_audit_service(WP_REST_Request $request): WP_REST_Response {
        return new WP_REST_Response([
            'success' => true,
            'message' => 'pong',
            'timestamp' => time()
        ], 200);
    }
    
    /**
     * Test URL discovery
     */
    public function test_url_discovery(WP_REST_Request $request): WP_REST_Response {
        try {
            $post_types = get_post_types(['public' => true], 'objects');
            $post_type_info = [];
            
            foreach ($post_types as $type => $obj) {
                if ($type === 'attachment') continue;
                $count = wp_count_posts($type);
                $post_type_info[$type] = [
                    'label' => $obj->label,
                    'count' => isset($count->publish) ? $count->publish : 0,
                ];
            }
            
            return new WP_REST_Response([
                'success' => true,
                'data' => [
                    'site_url' => site_url(),
                    'home_url' => home_url(),
                    'post_types' => $post_type_info,
                    'message' => __('Site information the local audit runner will crawl.', 'prorank-seo'),
                ],
            ], 200);
            
        } catch (\Exception $e) {
            return new WP_REST_Response([
                'success' => false,
                'error' => $e->getMessage(),
            ], 500);
        }
    }
    
    /**
     * Reset audit - clear all data
     */
    public function reset_audit(WP_REST_Request $request): WP_REST_Response {
        try {
            $audit_engine = new SiteAuditEngine();
            $result = $audit_engine->clear_audit_data();
            
            if ($result) {
                return new WP_REST_Response([
                    'success' => true,
                    'message' => __('Audit data has been reset', 'prorank-seo'),
                ], 200);
            }

            return new WP_REST_Response([
                'success' => false,
                'message' => __('Failed to reset audit data', 'prorank-seo'),
            ], 400);
        } catch (\Exception $e) {
            prorank_log('ProRank SEO: Reset audit error: ' . $e->getMessage());
            return new WP_REST_Response([
                'success' => false,
                'message' => $e->getMessage(),
            ], 500);
        }
    }
    public function get_audit_settings(WP_REST_Request $request): WP_REST_Response {
        $defaults = [
            'crawl_speed' => 30,
            'concurrent_requests' => 5,
            'request_timeout' => 5,
            'execute_javascript' => false,
            'check_images' => true,
            'check_css' => true,
            'check_javascript' => true,
            'follow_non_canonical' => false,
            'follow_nofollow' => false,
            'check_external_links' => false,
            'remove_url_parameters' => true,
            'max_pages' => 100,
            'max_duration' => 30,
            'max_depth' => 5,
            'max_folder_depth' => 10,
            'max_url_length' => 2048,
            'max_query_params' => 5,
            'ignore_robots_txt' => false,
            'include_patterns' => '',
            'exclude_patterns' => '',
            'check_types' => [
                'meta_tags' => true,
                'broken_links' => true,
                'image_optimization' => true,
                'page_speed' => true,
                'mobile_friendly' => true,
                'https_status' => true,
                'schema_validation' => true,
                'content_quality' => true,
            ],
        ];
        
        $saved_settings = get_option('prorank_audit_settings', []);
        
        // Deep merge settings
        $settings = $defaults;
        foreach ($saved_settings as $key => $value) {
            if ($key === 'check_types' && is_array($value)) {
                $settings['check_types'] = array_merge($defaults['check_types'], $value);
            } else {
                $settings[$key] = $value;
            }
        }
        
        return new WP_REST_Response($settings, 200);
    }
    
    /**
     * Save audit settings
     */
    public function save_audit_settings(WP_REST_Request $request): WP_REST_Response {
        $settings = [];
        
        // Get all parameters
        $params = $request->get_params();
        
        // Define allowed settings
        $allowed_settings = [
            'crawl_speed', 'concurrent_requests', 'request_timeout',
            'execute_javascript', 'check_images', 'check_css', 'check_javascript',
            'follow_non_canonical', 'follow_nofollow', 'check_external_links',
            'remove_url_parameters', 'max_pages', 'max_duration', 'max_depth',
            'max_folder_depth', 'max_url_length', 'max_query_params',
            'ignore_robots_txt', 'include_patterns', 'exclude_patterns', 'check_types'
        ];
        
        // Extract only allowed settings
        foreach ($allowed_settings as $key) {
            if (isset($params[$key])) {
                $settings[$key] = $params[$key];
            }
        }
        
        // Save settings
        update_option('prorank_audit_settings', $settings);
        
        return new WP_REST_Response([
            'success' => true,
            'message' => __('Settings saved successfully', 'prorank-seo'),
            'settings' => $settings,
        ], 200);
    }

}
