<?php

/**
 * Advanced Site Audit Module
 *
 * Comprehensive site audit engine with crawler and multi-point analysis
 *
 * @package ProRank\SEO\Modules\Tools
 * @since   1.0.0
 */

declare(strict_types=1);

namespace ProRank\SEO\Modules\Tools;

use ProRank\SEO\Modules\BaseModule;
use ProRank\SEO\Core\Audits\SiteAuditEngine;
use WP_Error;

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


/**
 * SiteAuditModule class
 */


class SiteAuditModule extends BaseModule {
    
    /**
     * Module slug
     *
     * @var string
     */
    protected string $slug = 'site-audit';
    
    /**
     * Module name
     *
     * @var string
     */
    protected string $name = 'Comprehensive Site Audit';
    
    /**
     * Module description
     *
     * @var string
     */
    protected string $description = 'Complete site health check with crawler and multi-point analysis';
    
    /**
     * Required tier
     *
     * @var string
     */
    protected string $feature_tier = 'free';
    
    /**
     * Parent module
     *
     * @var string|null
     */
    protected ?string $parent_slug = null;
    
    /**
     * Site audit engine
     *
     * @var SiteAuditEngine|null
     */
    private ?SiteAuditEngine $engine = null;
    
    /**
     * Action Scheduler actions
     */
    private const ACTION_START_AUDIT = 'prorank_audit_start';
    private const ACTION_CRAWL_BATCH = 'prorank_audit_crawl';
    private const ACTION_CHECK_URL = 'prorank_audit_check_url';
    private const ACTION_COMPLETE_AUDIT = 'prorank_audit_complete';
    
    /**
     * Audit states
     */
    private const STATE_IDLE = 'idle';
    private const STATE_CRAWLING = 'crawling';
    private const STATE_CHECKING = 'checking';
    private const STATE_COMPLETED = 'completed';
    private const STATE_FAILED = 'failed';
    
    /**
     * Check types
     */
    private const CHECK_TYPES = [
        'meta_tags',
        'schema_validation',
        'broken_links',
        'accessibility',
        'gsc_status',
        'https_status',
        'page_speed',
        'mobile_friendly',
        'content_quality',
        'duplicate_content',
        'core_web_vitals',
        'image_optimization',
        'structured_data',
        'redirect_chains',
        'robots_meta',
        'canonical_tags',
        'hreflang_tags',
        'open_graph',
        'twitter_cards',
        'heading_structure',
        'internal_links',
        'external_links',
        'xml_sitemap',
        'page_size',
        'compression',
        'caching_headers',
        'security_headers',
        'mixed_content',
        'viewport_meta',
        'lang_attribute',
        'readability',
        'keyword_optimization',
    ];
    
    /**
     * Check if module is standalone
     *
     * @return bool
     */
    public function is_standalone(): bool {
        return false;
    }
    
    /**
     * Initialize module hooks (required by ModuleInterface)
     *
     * @return void
     */
    public function init_hooks(): void {
        // Register REST routes
        add_action('rest_api_init', [$this, 'register_rest_routes']);
        add_filter('cron_schedules', [$this, 'register_audit_cron_schedules']);
        
        // Register action scheduler hooks
        add_action(self::ACTION_CRAWL_BATCH, [$this, 'handle_crawl_batch']);
        add_action(self::ACTION_CHECK_URL, [$this, 'handle_check_url'], 10, 3);
        add_action(self::ACTION_COMPLETE_AUDIT, [$this, 'handle_complete_audit']);
        
        add_action('prorank_audit_scheduled_run', [$this, 'handle_scheduled_audit']);
        add_action('prorank_audit_scheduled_alert', [$this, 'maybe_send_alert_from_status']);
        
        // Schedule recurring audit if enabled
        $this->maybe_schedule_recurring_audit();
    }
    
    /**
     * Initialize module
     *
     * @return void
     */
    public function init(): void {
        parent::init();
        
        // Initialize engine
        try {
            if (class_exists('\ProRank\SEO\Core\Audits\SiteAuditEngine')) {
                $this->engine = new SiteAuditEngine();
            }
        } catch (\Exception $e) {
            prorank_log('[ProRank SEO] Failed to initialize SiteAuditEngine: ' . $e->getMessage());
        }
        
        // Create database tables
        $this->create_tables();
    }
    
    /**
     * Register REST routes
     *
     * @return void
     */
    public function register_rest_routes(): void {
        $namespace = 'prorank-seo/v1';
        
        // Test endpoint
        register_rest_route($namespace, '/audit/test', [
            'methods' => 'GET',
            'callback' => [$this, 'rest_test'],
            'permission_callback' => [$this, 'rest_permission_check'],
        ]);
        
        // Status endpoint
        register_rest_route($namespace, '/audit/status', [
            'methods' => 'GET',
            'callback' => [$this, 'rest_get_status'],
            'permission_callback' => [$this, 'rest_permission_check'],
        ]);
        
        // Start audit endpoint
        register_rest_route($namespace, '/audit/start', [
            'methods' => 'POST',
            'callback' => [$this, 'rest_start_audit'],
            'permission_callback' => [$this, 'rest_permission_check'],
        ]);
        
        // Stop audit endpoint
        register_rest_route($namespace, '/audit/stop', [
            'methods' => 'POST',
            'callback' => [$this, 'rest_stop_audit'],
            'permission_callback' => [$this, 'rest_permission_check'],
        ]);
        
        register_rest_route($namespace, '/audit/reset', [
            'methods' => 'POST',
            'callback' => [$this, 'rest_reset_audit'],
            'permission_callback' => [$this, 'rest_permission_check'],
        ]);

        register_rest_route($namespace, '/audit/webhook', [
            'methods' => 'POST',
            'callback' => [$this, 'rest_handle_webhook'],
            'permission_callback' => '__return_true',
        ]);
        
        // Issues endpoint
        register_rest_route($namespace, '/audit/issues', [
            'methods' => 'GET',
            'callback' => [$this, 'rest_get_issues'],
            'permission_callback' => [$this, 'rest_permission_check'],
        ]);
        
        // History endpoint
        register_rest_route($namespace, '/audit/history', [
            'methods' => 'GET',
            'callback' => [$this, 'rest_get_history'],
            'permission_callback' => [$this, 'rest_permission_check'],
        ]);
        
        // Settings endpoints
        register_rest_route($namespace, '/audit/settings', [
            'methods' => 'GET',
            'callback' => [$this, 'rest_get_settings'],
            'permission_callback' => [$this, 'rest_permission_check'],
        ]);
        
        register_rest_route($namespace, '/audit/settings', [
            'methods' => 'POST',
            'callback' => [$this, 'rest_save_settings'],
            'permission_callback' => [$this, 'rest_permission_check'],
        ]);
        
        // Auto-fix endpoint
        register_rest_route($namespace, '/audit/autofix', [
            'methods' => 'POST',
            'callback' => [$this, 'rest_autofix_issue'],
            'permission_callback' => [$this, 'rest_permission_check'],
        ]);
        
        // Quick fixes endpoint
        register_rest_route($namespace, '/audit/quick-fixes', [
            'methods' => 'GET',
            'callback' => [$this, 'rest_get_quick_fixes'],
            'permission_callback' => [$this, 'rest_permission_check'],
        ]);
    }
    
    /**
     * REST permission check
     *
     * @return bool
     */
    public function rest_permission_check(): bool {
        return current_user_can('manage_options');
    }
    
    /**
     * Test endpoint
     *
     * @return array
     */
    public function rest_test(): array {
        return [
            'success' => true,
            'message' => 'Site Audit API is working',
            'version' => '3.0',
            'engine_loaded' => $this->engine !== null,
            'tables_exist' => $this->check_tables_exist(),
            'check_types' => self::CHECK_TYPES,
        ];
    }

    /**
     * Handle unsupported webhook callbacks.
     *
     * @param \WP_REST_Request $request REST request.
     * @return array|\WP_Error
     */
    public function rest_handle_webhook(\WP_REST_Request $request) {
        return new \WP_Error(
            'free_package_only',
            __('Webhook-based audit callbacks are not available in the free plugin.', 'prorank-seo'),
            ['status' => 403]
        );
    }

    /**
     * Resolve requested audit mode.
     *
     * @param \WP_REST_Request|null $request REST request.
     * @return string
     */
    private function get_requested_audit_mode(?\WP_REST_Request $request = null): string {
        return 'local';
    }

    /**
     * Check if current request targets a non-local audit mode.
     *
     * @param \WP_REST_Request|null $request REST request.
     * @return bool
     */
    private function is_cloud_mode_request(?\WP_REST_Request $request = null): bool {
        return $this->get_requested_audit_mode($request) === 'cloud';
    }

    /**
     * Reset local runtime state when an audit has already ended or failed.
     *
     * The local audit runner stores its in-flight state in options. If a crawl
     * dies mid-request, those flags can outlive the actual job and block the
     * next audit start even though nothing is still running.
     *
     * @return void
     */
    private function reset_local_audit_runtime_state(): void {
        $this->clear_all_audit_crons();

        update_option('prorank_audit_state', self::STATE_IDLE);
        delete_option('prorank_audit_current_id');
        delete_option('prorank_audit_start_time');
        delete_option('prorank_audit_last_progress');
        delete_option('prorank_audit_inline_processing');

        delete_transient('prorank_audit_status');
        delete_transient('prorank_audit_progress');
        delete_transient('prorank_dashboard_stats');
    }

    /**
     * Reconcile local runtime options with persisted audit rows.
     *
     * @return array{state:string,audit_id:?string}
     */
    private function reconcile_local_audit_runtime_state(): array {
        global $wpdb;

        $state = (string) get_option('prorank_audit_state', self::STATE_IDLE);
        $current_audit_id = trim((string) get_option('prorank_audit_current_id', ''));

        if ($state === self::STATE_IDLE) {
            return [
                'state' => self::STATE_IDLE,
                'audit_id' => $current_audit_id !== '' ? $current_audit_id : null,
            ];
        }

        if (in_array($state, [self::STATE_FAILED, self::STATE_COMPLETED], true)) {
            $this->reset_local_audit_runtime_state();

            return [
                'state' => self::STATE_IDLE,
                'audit_id' => null,
            ];
        }

        if ($current_audit_id === '') {
            $this->reset_local_audit_runtime_state();

            return [
                'state' => self::STATE_IDLE,
                'audit_id' => null,
            ];
        }

        $table_audits = $wpdb->prefix . 'prorank_audits';
        if (!$this->table_exists($table_audits)) {
            $this->reset_local_audit_runtime_state();

            return [
                'state' => self::STATE_IDLE,
                'audit_id' => null,
            ];
        }

        $audit = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT id, status, end_time
                 FROM %i
                 WHERE id = %s
                 LIMIT 1",
                $table_audits,
                $current_audit_id
            )
        );

        if (!$audit) {
            $this->reset_local_audit_runtime_state();

            return [
                'state' => self::STATE_IDLE,
                'audit_id' => null,
            ];
        }

        $audit_status = strtolower(trim((string) ($audit->status ?? '')));
        if (
            $audit_status === self::STATE_FAILED
            || $audit_status === self::STATE_COMPLETED
            || $audit_status === 'stopped'
            || !empty($audit->end_time)
        ) {
            $this->reset_local_audit_runtime_state();

            return [
                'state' => self::STATE_IDLE,
                'audit_id' => null,
            ];
        }

        if (!in_array($state, [self::STATE_CRAWLING, self::STATE_CHECKING], true)) {
            $this->reset_local_audit_runtime_state();

            return [
                'state' => self::STATE_IDLE,
                'audit_id' => null,
            ];
        }

        return [
            'state' => $state,
            'audit_id' => $current_audit_id,
        ];
    }

    /**
     * Resolve cloud audit ID from request, stored option, or history.
     *
     * @param \WP_REST_Request|null $request REST request.
     * @return string|null
     */
    private function resolve_cloud_audit_id(?\WP_REST_Request $request = null): ?string {
        return null;
    }

    /**
     * Get normalized cloud audit results, preferring cached results.
     *
     * @param string $audit_id Audit ID.
     * @return array
     */
    private function get_cloud_audit_results(string $audit_id): array {
        return [];
    }

    /**
     * Map cloud status to local dashboard state.
     *
     * @param string $status Cloud status.
     * @return string
     */
    private function map_cloud_status_to_state(string $status): string {
        switch (strtolower($status)) {
            case 'pending':
                return self::STATE_IDLE;
            case 'started':
            case 'running':
            case 'crawling':
                return self::STATE_CRAWLING;
            case 'analyzing':
            case 'checking':
                return self::STATE_CHECKING;
            case 'completed':
                return self::STATE_COMPLETED;
            case 'failed':
            case 'error':
                return self::STATE_FAILED;
            default:
                return self::STATE_IDLE;
        }
    }

    /**
     * Build a consistent cloud status payload for the dashboard.
     *
     * @param array      $audit   Cloud audit history/current row.
     * @param array|null $results Normalized cached results.
     * @return array
     */
    private function build_cloud_status_response(array $audit, ?array $results = null): array {
        $issue_counts = is_array($results['issue_counts'] ?? null) ? $results['issue_counts'] : (array) ($audit['issues'] ?? []);
        $stats = is_array($results['statistics'] ?? null) ? $results['statistics'] : [];
        $status = (string) ($audit['status'] ?? 'completed');
        $state = $this->map_cloud_status_to_state($status);
        $total_urls = (int) ($stats['total_urls'] ?? $audit['total_urls'] ?? $audit['analyzed_urls'] ?? $audit['crawled_urls'] ?? 0);
        $checked_urls = (int) ($audit['analyzed_urls'] ?? $audit['crawled_urls'] ?? $total_urls);
        $progress = (float) ($audit['progress'] ?? ($state === self::STATE_COMPLETED ? 100 : 0));
        $score = $results['raw_overall_score'] ?? $results['overall_score'] ?? $results['normalized_overall_score'] ?? $audit['overall_score'] ?? 0;

        $stats = array_merge([
            'total_urls' => $total_urls,
            'pages_scanned' => (int) ($audit['analyzed_urls'] ?? $total_urls),
            'pages_analyzed' => (int) ($audit['analyzed_urls'] ?? $total_urls),
            'critical_issues' => (int) ($issue_counts['critical'] ?? 0),
            'high_issues' => (int) ($issue_counts['high'] ?? 0),
            'medium_issues' => (int) ($issue_counts['medium'] ?? 0),
            'low_issues' => (int) ($issue_counts['low'] ?? 0),
            'warning_issues' => (int) ($issue_counts['warning'] ?? 0),
            'total_issues' => (int) ($issue_counts['total'] ?? 0),
            'passed' => (int) ($stats['passed'] ?? 0),
        ], $stats);

        return [
            'audit_id' => (string) ($audit['id'] ?? ''),
            'state' => $state,
            'status' => $status,
            'can_start' => !in_array($state, [self::STATE_CRAWLING, self::STATE_CHECKING], true),
            'progress' => [
                'percent' => $progress,
                'percentage' => $progress,
                'total_urls' => $total_urls,
                'checked_urls' => $checked_urls,
                'issues_found' => (int) ($issue_counts['total'] ?? 0),
                'estimated_time_remaining' => '—',
            ],
            'started_at' => $audit['started_at'] ?? $audit['created_at'] ?? null,
            'completed_at' => $audit['completed_at'] ?? null,
            'score' => (int) $score,
            'overallScore' => (int) $score,
            'overall_score' => (int) $score,
            'issuesCritical' => (int) ($issue_counts['critical'] ?? 0),
            'issuesHigh' => (int) ($issue_counts['high'] ?? 0),
            'issuesMedium' => (int) ($issue_counts['medium'] ?? 0),
            'issuesLow' => (int) ($issue_counts['low'] ?? 0),
            'issuesWarning' => (int) ($issue_counts['warning'] ?? 0),
            'totalUrls' => $total_urls,
            'total_urls' => $total_urls,
            'stats' => $stats,
            'categories' => $this->get_empty_categories(),
        ];
    }

    /**
     * Format a cloud history entry for the admin UI.
     *
     * @param array      $audit    Cloud history row.
     * @param array|null $results  Normalized cached results.
     * @param array|null $previous Previous formatted row.
     * @return array
     */
    private function format_cloud_history_entry(array $audit, ?array $results = null, ?array $previous = null): array {
        $issue_counts = is_array($results['issue_counts'] ?? null) ? $results['issue_counts'] : (array) ($audit['issues'] ?? []);
        $stats = is_array($results['statistics'] ?? null) ? $results['statistics'] : [];
        $total_urls = (int) ($stats['total_urls'] ?? $audit['total_urls'] ?? $audit['analyzed_urls'] ?? $audit['crawled_urls'] ?? 0);
        $score = $results['raw_overall_score'] ?? $results['overall_score'] ?? $results['normalized_overall_score'] ?? $audit['overall_score'] ?? 0;
        $start_time = $audit['started_at'] ?? $audit['created_at'] ?? null;
        $end_time = $audit['completed_at'] ?? null;

        $entry = [
            'id' => (string) ($audit['id'] ?? ''),
            'status' => (string) ($audit['status'] ?? 'completed'),
            'state' => (string) ($audit['state'] ?? $this->map_cloud_status_to_state((string) ($audit['status'] ?? 'completed'))),
            'score' => (int) $score,
            'overall_score' => (int) $score,
            'start_time' => $start_time,
            'end_time' => $end_time,
            'completed_at' => $end_time,
            'duration' => ($start_time && $end_time)
                ? human_time_diff(strtotime((string) $start_time), strtotime((string) $end_time))
                : __('In progress', 'prorank-seo'),
            'total_urls' => $total_urls,
            'critical' => (int) ($issue_counts['critical'] ?? 0),
            'high' => (int) ($issue_counts['high'] ?? 0),
            'medium' => (int) ($issue_counts['medium'] ?? 0),
            'low' => (int) ($issue_counts['low'] ?? 0),
            'warnings' => (int) ($issue_counts['warning'] ?? 0),
            'total_issues' => (int) ($issue_counts['total'] ?? 0),
            'issues' => $issue_counts,
            'stats' => array_merge([
                'total_urls' => $total_urls,
                'pages_scanned' => (int) ($audit['analyzed_urls'] ?? $total_urls),
                'pages_analyzed' => (int) ($audit['analyzed_urls'] ?? $total_urls),
                'critical_issues' => (int) ($issue_counts['critical'] ?? 0),
                'high_issues' => (int) ($issue_counts['high'] ?? 0),
                'medium_issues' => (int) ($issue_counts['medium'] ?? 0),
                'low_issues' => (int) ($issue_counts['low'] ?? 0),
                'warning_issues' => (int) ($issue_counts['warning'] ?? 0),
                'total_issues' => (int) ($issue_counts['total'] ?? 0),
            ], $stats),
        ];

        if (is_array($previous)) {
            $entry['delta_score'] = (int) $entry['score'] - (int) ($previous['score'] ?? 0);
            $entry['delta_issues'] = (int) $entry['total_issues'] - (int) ($previous['total_issues'] ?? 0);
        }

        return $entry;
    }
    
    /**
     * Get audit status
     *
     * @return array|\WP_Error
     */
    public function rest_get_status(\WP_REST_Request $request = null) {
        try {
            // Fallback to local audit system (old system)
            // Ensure tables exist
            $this->create_tables();

            $runtime_state = $this->reconcile_local_audit_runtime_state();
            $state = $runtime_state['state'];
            $current_audit_id = $runtime_state['audit_id'];

            if ($current_audit_id && in_array($state, [self::STATE_CRAWLING, self::STATE_CHECKING], true)) {
                $this->maybe_run_inline_processing($current_audit_id);
            }

            $response_state = $state;
            if ($state === self::STATE_FAILED) {
                $response_state = 'error';
            }

            $response = [
                'state'       => $response_state,
                'audit_id'    => $current_audit_id,
                'can_start'   => $state === self::STATE_IDLE,
                'progress'    => [
                    'percent'      => 0,
                    'checked_urls' => 0,
                    'total_urls'   => 0,
                ],
                'stats'       => [
                    'urls_found'    => 0,
                    'urls_checked'  => 0,
                    'issues_found'  => 0,
                    'warnings_found'=> 0,
                    'passed_checks' => 0,
                ],
                'categories'  => $this->get_empty_categories(),
            ];

            $resolved_audit_id = $current_audit_id;

            if (!$resolved_audit_id) {
                $table_audits = $wpdb->prefix . 'prorank_audits';
                $resolved_audit_id = $wpdb->get_var(
                    "SELECT id FROM {$table_audits} ORDER BY end_time DESC, start_time DESC LIMIT 1"
                );
            }

            if ($resolved_audit_id) {
                global $wpdb;
                $table_audits = $wpdb->prefix . 'prorank_audits';
                $table_urls = $wpdb->prefix . 'prorank_audit_urls';
                $table_issues = $wpdb->prefix . 'prorank_audit_issues';
                
                // Get audit stats
                $audit = $wpdb->get_row($wpdb->prepare(
                    "SELECT * FROM {$table_audits} WHERE id = %s",
                    $resolved_audit_id
                ));
                
                if ($audit) {
                    $current_audit_id = $resolved_audit_id;
                    $state = $audit->status ?: $state;
                    $response_state = $state === self::STATE_FAILED ? 'error' : $state;
                    $response['state'] = $response_state;
                    $response['audit_id'] = $current_audit_id;
                    $response['can_start'] = !in_array($response_state, [self::STATE_CRAWLING, self::STATE_CHECKING], true);

                    $urls_found = (int) $wpdb->get_var($wpdb->prepare(
                        "SELECT COUNT(*) FROM {$table_urls} WHERE audit_id = %s",
                        $current_audit_id
                    ));
                    
                    $urls_checked = (int) $wpdb->get_var($wpdb->prepare(
                        "SELECT COUNT(*) FROM {$table_urls} WHERE audit_id = %s AND status IN ('checked','completed')",
                        $current_audit_id
                    ));
                    
                    // Prevent negative remaining/over 100% when counts drift
                    if ($urls_checked > $urls_found) {
                        $urls_found = $urls_checked;
                    }
                    
                    $issues_found = (int) $wpdb->get_var($wpdb->prepare(
                        "SELECT COUNT(*) FROM {$table_issues} WHERE audit_id = %s AND severity IN ('critical', 'high', 'medium', 'low')",
                        $current_audit_id
                    ));
                    
                    $warnings_found = (int) $wpdb->get_var($wpdb->prepare(
                        "SELECT COUNT(*) FROM {$table_issues} WHERE audit_id = %s AND severity = 'warning'",
                        $current_audit_id
                    ));
                    
                    $passed_checks = (int) $wpdb->get_var($wpdb->prepare(
                        "SELECT COUNT(*) FROM {$table_issues} WHERE audit_id = %s AND severity = 'passed'",
                        $current_audit_id
                    ));
                    
                    $checked = min($urls_checked, $urls_found);
                    $progress_percent = $urls_found > 0 ? min(100, round(($checked / max(1, $urls_found)) * 100)) : 0;
                    $response['progress'] = [
                        'percent'      => $progress_percent,
                        'checked_urls' => $urls_checked,
                        'total_urls'   => $urls_found,
                    ];
                    $response['stats']      = [
                        'urls_found'    => $urls_found,
                        'urls_checked'  => $urls_checked,
                        'issues_found'  => $issues_found,
                        'warnings_found'=> $warnings_found,
                        'passed_checks' => $passed_checks,
                    ];
                    $response['score']      = $audit->score ?? 0;
                    $response['start_time'] = $audit->start_time;
                    if (!empty($audit->end_time)) {
                        $response['completed_at'] = $audit->end_time;
                    }
                    $response['categories'] = $this->get_category_stats($current_audit_id);

                    update_option('prorank_audit_last_progress', [
                        'checked' => $urls_checked,
                        'time' => time(),
                    ], false);
                }
            }
            
            return $response;
            
        } catch (\Exception $e) {
            prorank_log('[ProRank SEO] Error in rest_get_status: ' . $e->getMessage());
            return new \WP_Error('audit_error', $e->getMessage());
        }
    }
    
    /**
     * Start audit
     *
     * @return array|\WP_Error
     */
    public function rest_start_audit(\WP_REST_Request $request = null) {
        try {
            // Ensure tables exist
            $this->create_tables();
            
            $runtime_state = $this->reconcile_local_audit_runtime_state();
            $state = $runtime_state['state'];

            if (in_array($state, [self::STATE_CRAWLING, self::STATE_CHECKING], true)) {
                return new \WP_Error(
                    'audit_running',
                    __('An audit is already running', 'prorank-seo'),
                    ['status' => 409]
                );
            }

            $payload = [];
            if ($request instanceof \WP_REST_Request) {
                $payload = $request->get_json_params();
            }

            if (!empty($payload['settings']) && is_array($payload['settings'])) {
                $settings = $payload['settings'];
                if (isset($settings['max_urls'])) {
                    update_option('prorank_audit_max_urls', max(10, (int) $settings['max_urls']));
                }
                if (isset($settings['max_depth'])) {
                    update_option('prorank_audit_max_depth', max(1, (int) $settings['max_depth']));
                }
                if (!empty($settings['check_types']) && is_array($settings['check_types'])) {
                    $valid_checks = array_flip(self::CHECK_TYPES);
                    $clean_checks = [];
                    foreach ($settings['check_types'] as $key => $value) {
                        if (isset($valid_checks[$key])) {
                            $clean_checks[$key] = (bool) $value;
                        }
                    }
                    update_option('prorank_audit_check_types', $clean_checks);
                }
            }
            
            // Create new audit
            global $wpdb;
            $table_audits = $wpdb->prefix . 'prorank_audits';

            // Get audit settings from saved options
            $audit_settings = [
                'max_urls' => (int) get_option('prorank_audit_max_urls', 500),
                'max_depth' => (int) get_option('prorank_audit_max_depth', 5),
            ];
            $check_types = get_option('prorank_audit_check_types', []);

            $audit_id = wp_generate_uuid4();
            $wpdb->insert(
                $table_audits,
                [
                    'id' => $audit_id,
                    'status' => self::STATE_CRAWLING,
                    'start_time' => current_time('mysql'),
                    'score' => 0,
                    'stats' => json_encode([
                        'urls_found' => 0,
                        'urls_checked' => 0,
                        'issues_found' => 0,
                        'warnings_found' => 0,
                        'passed_checks' => 0,
                    ]),
                    'options' => json_encode([
                        'settings' => $audit_settings,
                        'check_types' => $check_types,
                    ]),
                ],
                ['%s', '%s', '%s', '%d', '%s', '%s']
            );

            // Update options
            update_option('prorank_audit_state', self::STATE_CRAWLING);
            update_option('prorank_audit_current_id', $audit_id);
            update_option('prorank_audit_start_time', time());
            update_option('prorank_audit_last_progress', [
                'checked' => 0,
                'time' => time(),
            ], false);

            $inline_processing = (defined('DISABLE_WP_CRON') && DISABLE_WP_CRON) || !function_exists('as_enqueue_async_action');
            update_option('prorank_audit_inline_processing', $inline_processing, false);
            
            // Add initial URLs to crawl
            $this->add_initial_urls($audit_id);
            
            // Schedule crawl action
            if (function_exists('as_enqueue_async_action')) {
                as_enqueue_async_action(self::ACTION_CRAWL_BATCH, [$audit_id]);
            } else {
                // Fallback to direct call
                $this->handle_crawl_batch($audit_id);
            }

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

            if ($inline_processing) {
                $this->process_inline_batch($audit_id, 5);
            }
            
            return [
                'success' => true,
                'audit_id' => $audit_id,
                'message' => __('Audit started successfully', 'prorank-seo'),
            ];
            
        } catch (\Exception $e) {
            prorank_log('[ProRank SEO] Error in rest_start_audit: ' . $e->getMessage());
            return new \WP_Error('start_error', $e->getMessage());
        }
    }
    
    /**
     * Stop audit
     *
     * @return array|\WP_Error
     */
    public function rest_stop_audit(\WP_REST_Request $request = null) {
        try {
            // Use the engine if available
            if ($this->engine) {
                $result = $this->engine->stop_audit();
                if ($result) {
                    return [
                        'success' => true,
                        'message' => __('Audit stopped successfully', 'prorank-seo'),
                    ];
                }
            }
            
            // Fallback implementation with better cleanup
            global $wpdb;
            $table_audits = $wpdb->prefix . 'prorank_audits';
            
            // Find any running audits
            $running_audits = $wpdb->get_results(
                "SELECT id, audit_id FROM {$table_audits} 
                WHERE status IN ('crawling', 'checking', 'running') 
                ORDER BY start_time DESC"
            );
            
            if (empty($running_audits)) {
                // Check if there's a stuck audit based on options
                $current_id = get_option('prorank_audit_current_id');
                if ($current_id) {
                    $wpdb->update(
                        $table_audits,
                        [
                            'status' => 'stopped',
                            'end_time' => current_time('mysql'),
                        ],
                        ['id' => $current_id],
                        ['%s', '%s'],
                        ['%s']
                    );
                }
            } else {
                // Stop all running audits
                foreach ($running_audits as $audit) {
                    $wpdb->update(
                        $table_audits,
                        [
                            'status' => 'stopped',
                            'end_time' => current_time('mysql'),
                        ],
                        ['id' => $audit->id],
                        ['%s', '%s'],
                        ['%s']
                    );
                    
                    // Clean up scheduled events for this audit
                    $this->clear_audit_scheduled_events($audit->audit_id ?? $audit->id);
                }
            }
            
            // Clear all audit-related cron events
            $this->clear_all_audit_crons();
            
            // Reset state options
            update_option('prorank_audit_state', self::STATE_IDLE);
            delete_option('prorank_audit_current_id');
            delete_option('prorank_audit_start_time');
            
            // Clear any transients
            delete_transient('prorank_audit_status');
            delete_transient('prorank_audit_progress');
            
            return [
                'success' => true,
                'message' => __('Audit stopped and cleaned up successfully', 'prorank-seo'),
            ];
            
        } catch (\Exception $e) {
            prorank_log('[ProRank SEO] Error in rest_stop_audit: ' . $e->getMessage());
            return new \WP_Error('stop_error', $e->getMessage());
        }
    }
    
    /**
     * Reset audit - force clear all audit data and states
     *
     * @return array|\WP_Error
     */
    public function rest_reset_audit(\WP_REST_Request $request = null) {
        try {
            // Use the engine if available
            if ($this->engine) {
                $this->engine->clear_audit_data();
            }
            
            global $wpdb;
            $table_audits = $wpdb->prefix . 'prorank_audits';
            $table_urls = $wpdb->prefix . 'prorank_audit_urls';
            $table_issues = $wpdb->prefix . 'prorank_audit_issues';
            
            // Mark all running audits as stopped
            $wpdb->query(
                "UPDATE {$table_audits} 
                SET status = 'stopped', end_time = NOW() 
                WHERE status IN ('crawling', 'checking', 'running')"
            );
            
            // Clear all audit-related cron events
            $this->clear_all_audit_crons();
            
            // Reset all options
            update_option('prorank_audit_state', self::STATE_IDLE);
            delete_option('prorank_audit_current_id');
            delete_option('prorank_audit_start_time');
            
            // Clear all transients
            $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_%prorank_audit%'");
            $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_%prorank_audit%'");
            
            // Clear Action Scheduler tasks if available
            if (function_exists('as_unschedule_all_actions')) {
                $actions = [
                    self::ACTION_CRAWL_BATCH,
                    self::ACTION_CHECK_URL,
                    self::ACTION_COMPLETE_AUDIT,
                    'prorank_schedule_url_checks',
                    'prorank_check_audit_url',
                    'prorank_run_site_crawler',
                ];
                
                foreach ($actions as $action) {
                    as_unschedule_all_actions($action);
                }
            }
            
            return [
                'success' => true,
                'message' => __('Audit system has been reset successfully', 'prorank-seo'),
            ];
            
        } catch (\Exception $e) {
            prorank_log('[ProRank SEO] Error in rest_reset_audit: ' . $e->getMessage());
            return new \WP_Error('reset_error', $e->getMessage());
        }
    }
    
    /**
     * Get audit issues
     *
     * @param \WP_REST_Request $request
     * @return array|\WP_Error
     */
    public function rest_get_issues($request) {
        try {
            if ($this->is_cloud_mode_request($request)) {
                $audit_id = $this->resolve_cloud_audit_id($request);
                $severity = $request->get_param('severity') ?? 'all';
                $category = $request->get_param('category') ?? 'all';

                if (!$audit_id) {
                    return [
                        'audit_id' => null,
                        'issues' => [],
                        'stats' => [],
                    ];
                }

                $results = $this->get_cloud_audit_results($audit_id);
                $issues = array_values($results['issues'] ?? []);

                if ($severity !== 'all') {
                    $issues = array_values(array_filter($issues, static function ($issue) use ($severity) {
                        return (string) ($issue['severity'] ?? '') === (string) $severity;
                    }));
                }

                if ($category !== 'all') {
                    $issues = array_values(array_filter($issues, static function ($issue) use ($category) {
                        $candidate = (string) ($issue['display_category'] ?? $issue['category'] ?? 'other');

                        return $candidate === (string) $category;
                    }));
                }

                return [
                    'audit_id' => $audit_id,
                    'issues' => $issues,
                    'stats' => is_array($results['statistics'] ?? null) ? $results['statistics'] : [],
                    'issue_counts' => is_array($results['issue_counts'] ?? null) ? $results['issue_counts'] : [],
                    'pagination' => is_array($results['pagination'] ?? null) ? $results['pagination'] : [],
                ];
            }

            // Ensure tables exist
            $this->create_tables();
            
            $audit_id = $request->get_param('audit_id') ?? get_option('prorank_audit_current_id');
            $severity = $request->get_param('severity') ?? 'all';
            $category = $request->get_param('category') ?? 'all';
            
            if (!$audit_id) {
                return [];
            }
            
            global $wpdb;
            $table_issues = $wpdb->prefix . 'prorank_audit_issues';
            
            $where_conditions = ["audit_id = %s"];
            $where_values = [$audit_id];
            
            if ($severity !== 'all') {
                $where_conditions[] = "severity = %s";
                $where_values[] = $severity;
            } else {
                // Exclude passed checks by default
                $where_conditions[] = "severity != 'passed'";
            }
            
            $sql = "SELECT * FROM {$table_issues} WHERE " . implode(' AND ', $where_conditions) . " ORDER BY 
                    CASE severity 
                        WHEN 'critical' THEN 1 
                        WHEN 'high' THEN 2 
                        WHEN 'medium' THEN 3 
                        WHEN 'low' THEN 4 
                        WHEN 'warning' THEN 5 
                        ELSE 6 
                    END, url ASC";
            
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
            $issues = $wpdb->get_results($wpdb->prepare($sql, ...$where_values));
            
            // Format issues
            $formatted = [];
            foreach ($issues as $issue) {
                $issue_category = $this->get_issue_category($issue->type);
                
                if ($category !== 'all' && $issue_category !== $category) {
                    continue;
                }
                
                $formatted[] = [
                    'id' => $issue->id,
                    'url' => $issue->url,
                    'type' => $issue->type,
                    'message' => $issue->message,
                    'severity' => $issue->severity,
                    'category' => $issue_category,
                    'can_autofix' => $this->can_autofix($issue->type),
                    'data' => json_decode($issue->data, true) ?? [],
                ];
            }
            
            return $formatted;
            
        } catch (\Exception $e) {
            prorank_log('[ProRank SEO] Error in rest_get_issues: ' . $e->getMessage());
            return new \WP_Error('issues_error', $e->getMessage());
        }
    }
    
    /**
     * Get audit history
     *
     * @return array|\WP_Error
     */
    public function rest_get_history(\WP_REST_Request $request = null) {
        try {
            // Ensure tables exist
            $this->create_tables();
            
            global $wpdb;
            $table_audits = $wpdb->prefix . 'prorank_audits';
            $audit_columns = $this->get_table_columns($table_audits);
            $order_by = $this->get_audit_order_by_clause($audit_columns);
            
            $history = $wpdb->get_results(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- order clause is built from a vetted column allowlist.
                "SELECT * FROM {$table_audits}
                ORDER BY {$order_by}
                LIMIT 20"
            );
            
            // Format history with deltas vs previous run
            $formatted = [];
            $previous = null;
            foreach ($history as $audit) {
                $stats = json_decode($audit->stats, true) ?? [];
                $audit_id = (string) ($audit->audit_id ?? $audit->id ?? '');
                $started_at = $audit->started_at ?? $audit->start_time ?? null;
                $completed_at = $audit->completed_at ?? $audit->end_time ?? null;
                $entry = [
                    'id' => $audit_id,
                    'status' => $audit->status,
                    'score' => (int) $audit->score,
                    'source' => 'local',
                    'start_time' => $started_at,
                    'started_at' => $started_at,
                    'end_time' => $completed_at,
                    'completed_at' => $completed_at,
                    'stats' => $stats,
                    'duration' => ($started_at && $completed_at) ?
                        human_time_diff(strtotime((string) $started_at), strtotime((string) $completed_at)) :
                        'In progress',
                ];

                if ($previous) {
                    $entry['delta_score'] = (int) $audit->score - (int) ($previous->score ?? 0);
                    $prev_stats = json_decode((string) ($previous->stats ?? ''), true) ?? [];
                    $curr_issues = (int)($stats['total_issue_instances'] ?? 0);
                    $prev_issues = (int)($prev_stats['total_issue_instances'] ?? 0);
                    $entry['delta_issues'] = $curr_issues - $prev_issues;
                }

                $formatted[] = $entry;
                $previous = $audit;
            }
            
            return $formatted;
            
        } catch (\Exception $e) {
            prorank_log('[ProRank SEO] Error in rest_get_history: ' . $e->getMessage());
            return new \WP_Error('history_error', $e->getMessage());
        }
    }
    
    /**
     * Get settings
     *
     * @return array
     */
    public function rest_get_settings(): array {
        $saved_checks = get_option('prorank_audit_check_types', []);
        $default_checks = [
            'meta_tags' => true,
            'broken_links' => true,
            'image_optimization' => true,
            'page_speed' => false,
            'mobile_friendly' => false,
            'https_status' => true,
            'schema_validation' => true,
            'content_quality' => true,
        ];
        
        return [
            'max_urls' => (int) get_option('prorank_audit_max_urls', 500),
            'max_depth' => (int) get_option('prorank_audit_max_depth', 5),
            'check_types' => array_merge($default_checks, $saved_checks),
            'schedule_enabled' => (bool) get_option('prorank_audit_schedule_enabled', false),
            'schedule_frequency' => get_option('prorank_audit_schedule_frequency', 'weekly'),
            'schedule_time' => get_option('prorank_audit_schedule_time', '02:00'),
            'email_notifications' => (bool) get_option('prorank_audit_email_notifications', true),
            'email_critical_only' => (bool) get_option('prorank_audit_email_critical_only', false),
            'notification_emails' => (string) get_option('prorank_audit_notification_emails', ''),
            'enable_autofix' => (bool) get_option('prorank_audit_enable_autofix', false),
            'autofix_trailing_slashes' => (bool) get_option('prorank_audit_autofix_trailing_slashes', false),
            'autofix_transients' => (bool) get_option('prorank_audit_autofix_transients', false),
            'autofix_database' => (bool) get_option('prorank_audit_autofix_database', false),
            'autofix_mixed_content' => (bool) get_option('prorank_audit_autofix_mixed_content', false),
            'autofix_meta_descriptions' => (bool) get_option('prorank_audit_autofix_meta_descriptions', true),
            'autofix_alt_texts' => (bool) get_option('prorank_audit_autofix_alt_texts', true),
            'autofix_compression' => (bool) get_option('prorank_audit_autofix_compression', true),
            'autofix_caching' => (bool) get_option('prorank_audit_autofix_caching', true),
            'available_checks' => self::CHECK_TYPES,
        ];
    }
    
    /**
     * Save settings
     *
     * @param \WP_REST_Request $request
     * @return array
     */
    public function rest_save_settings($request): array {
        $settings = $request->get_json_params();
        
        if (isset($settings['max_urls'])) {
            update_option('prorank_audit_max_urls', (int) $settings['max_urls']);
        }
        
        if (isset($settings['max_depth'])) {
            update_option('prorank_audit_max_depth', (int) $settings['max_depth']);
        }
        
        if (isset($settings['check_types'])) {
            update_option('prorank_audit_check_types', $settings['check_types']);
        }
        
        if (isset($settings['schedule_enabled'])) {
            update_option('prorank_audit_schedule_enabled', (bool) $settings['schedule_enabled']);
        }
        
        if (isset($settings['schedule_frequency'])) {
            update_option('prorank_audit_schedule_frequency', $settings['schedule_frequency']);
        }

        if (isset($settings['schedule_time']) && is_string($settings['schedule_time'])) {
            $schedule_time = preg_match('/^(?:[01]\d|2[0-3]):[0-5]\d$/', $settings['schedule_time'])
                ? $settings['schedule_time']
                : '02:00';
            update_option('prorank_audit_schedule_time', $schedule_time);
        }

        if (isset($settings['email_notifications'])) {
            update_option('prorank_audit_email_notifications', (bool) $settings['email_notifications']);
        }

        if (isset($settings['email_critical_only'])) {
            update_option('prorank_audit_email_critical_only', (bool) $settings['email_critical_only']);
        }

        if (isset($settings['notification_emails'])) {
            update_option('prorank_audit_notification_emails', sanitize_text_field((string) $settings['notification_emails']));
        }

        if (isset($settings['enable_autofix'])) {
            update_option('prorank_audit_enable_autofix', (bool) $settings['enable_autofix']);
        }

        $autofix_keys = [
            'autofix_trailing_slashes',
            'autofix_transients',
            'autofix_database',
            'autofix_mixed_content',
            'autofix_meta_descriptions',
            'autofix_alt_texts',
            'autofix_compression',
            'autofix_caching',
        ];
        foreach ($autofix_keys as $autofix_key) {
            if (isset($settings[$autofix_key])) {
                update_option('prorank_audit_' . $autofix_key, (bool) $settings[$autofix_key]);
            }
        }

        // Reschedule recurring audit based on new settings
        $this->maybe_schedule_recurring_audit();
        
        return [
            'success' => true,
            'message' => __('Settings saved successfully', 'prorank-seo'),
        ];
    }
    
    /**
     * Auto-fix issue
     *
     * @param \WP_REST_Request $request
     * @return array|\WP_Error
     */
    public function rest_autofix_issue($request) {
        try {
            $issue_id = $request->get_param('issue_id');
            $issue_type = $request->get_param('issue_type');
            
            if (!$issue_id || !$issue_type) {
                return new \WP_Error('missing_params', __('Missing required parameters', 'prorank-seo'));
            }
            
            if (!$this->can_autofix($issue_type)) {
                return new \WP_Error('not_fixable', __('This issue cannot be auto-fixed', 'prorank-seo'));
            }
            
            $result = $this->apply_autofix($issue_type, $issue_id);
            
            if ($result) {
                return [
                    'success' => true,
                    'message' => __('Issue fixed successfully', 'prorank-seo'),
                ];
            } else {
                return new \WP_Error('fix_failed', __('Failed to fix issue', 'prorank-seo'));
            }
            
        } catch (\Exception $e) {
            prorank_log('[ProRank SEO] Error in rest_autofix_issue: ' . $e->getMessage());
            return new \WP_Error('autofix_error', $e->getMessage());
        }
    }
    
    /**
     * Get quick fixes
     *
     * @return array|\WP_Error
     */
    public function rest_get_quick_fixes() {
        try {
            $audit_id = get_option('prorank_audit_current_id');
            
            if (!$audit_id) {
                return [];
            }
            
            global $wpdb;
            $table_issues = $wpdb->prefix . 'prorank_audit_issues';
            
            // Get fixable issues
            $fixable_types = ['no_compression', 'no_caching', 'missing_meta_description', 'missing_alt_text'];
            $placeholders = implode(',', array_fill(0, count($fixable_types), '%s'));
            $issues = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT type, COUNT(*) as count, severity 
                    FROM {$table_issues} 
                    WHERE audit_id = %s 
                    AND type IN ({$placeholders})
                    GROUP BY type, severity
                    ORDER BY 
                        CASE severity 
                            WHEN 'critical' THEN 1 
                            WHEN 'high' THEN 2 
                            WHEN 'medium' THEN 3 
                            WHEN 'low' THEN 4 
                            ELSE 5 
                        END",
                    ...array_merge([$audit_id], $fixable_types)
                )
            );
            
            $fixes = [];
            foreach ($issues as $issue) {
                $fixes[] = [
                    'type' => $issue->type,
                    'count' => (int) $issue->count,
                    'severity' => $issue->severity,
                    'title' => $this->get_fix_title($issue->type),
                    'description' => $this->get_fix_description($issue->type),
                    'impact' => $this->get_fix_impact($issue->type),
                ];
            }
            
            return $fixes;
            
        } catch (\Exception $e) {
            prorank_log('[ProRank SEO] Error in rest_get_quick_fixes: ' . $e->getMessage());
            return new \WP_Error('quick_fixes_error', $e->getMessage());
        }
    }
    
    /**
     * Update settings (alias for rest_save_settings)
     *
     * @param \WP_REST_Request $request
     * @return array
     */
    public function rest_update_settings($request): array {
        return $this->rest_save_settings($request);
    }
    
    /**
     * Auto-fix endpoint
     *
     * @param \WP_REST_Request $request
     * @return array|\WP_Error
     */
    public function rest_auto_fix($request) {
        return $this->rest_autofix_issue($request);
    }
    
    /**
     * Handle crawl batch
     *
     * @param string $audit_id
     * @return void
     */
    public function handle_crawl_batch(string $audit_id): void {
        try {
            if ($this->engine) {
                $this->engine->crawl_batch($audit_id);
            } else {
                // Fallback implementation
                $this->simple_crawl_batch($audit_id);
            }
        } catch (\Exception $e) {
            prorank_log('[ProRank SEO] Error in handle_crawl_batch: ' . $e->getMessage());
            update_option('prorank_audit_state', self::STATE_FAILED);
        }
    }
    
    /**
     * Handle check URL
     *
     * @param string $audit_id
     * @param int $url_id
     * @param string $url
     * @return void
     */
    public function handle_check_url(string $audit_id, int $url_id, string $url): void {
        try {
            if ($this->engine) {
                $this->engine->check_url($audit_id, $url_id, $url);
            } else {
                // Fallback implementation
                $this->simple_check_url($audit_id, $url_id, $url);
            }
        } catch (\Exception $e) {
            prorank_log('[ProRank SEO] Error in handle_check_url: ' . $e->getMessage());
        }
    }
    
    /**
     * Handle complete audit
     *
     * @param string $audit_id
     * @return void
     */
    public function handle_complete_audit(string $audit_id): void {
        try {
            if ($this->engine) {
                $this->engine->complete_audit($audit_id);
            } else {
                // Fallback implementation
                $this->simple_complete_audit($audit_id);
            }
        } catch (\Exception $e) {
            prorank_log('[ProRank SEO] Error in handle_complete_audit: ' . $e->getMessage());
        }

        // Keep dashboard/admin-bar cards synced with the latest audit summary.
        delete_transient('prorank_dashboard_stats');

        // Send alert summary
        $this->maybe_send_alert($audit_id);
    }
    
    /**
     * Simple crawl batch implementation
     *
     * @param string $audit_id
     * @return void
     */
    private function simple_crawl_batch(string $audit_id): void {
        global $wpdb;
        $table_urls = $wpdb->prefix . 'prorank_audit_urls';
        
        // Get pending URLs - increased batch size for faster processing
        $pending_urls = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM {$table_urls} 
            WHERE audit_id = %s AND status = 'pending' 
            ORDER BY depth ASC 
            LIMIT 25",
            $audit_id
        ));
        
        if (empty($pending_urls)) {
            // No more URLs to crawl, transition to checking
            $this->transition_to_checking($audit_id);
            return;
        }
        
        foreach ($pending_urls as $url_row) {
            // Mark as crawled
            $wpdb->update(
                $table_urls,
                ['status' => 'crawled'],
                ['id' => $url_row->id],
                ['%s'],
                ['%d']
            );
        }
        
        // Schedule next batch
        if (function_exists('as_enqueue_async_action')) {
            as_enqueue_async_action(self::ACTION_CRAWL_BATCH, [$audit_id]);
        }
    }

    /**
     * Run inline audit processing when background jobs are not running.
     *
     * @param string $audit_id
     * @return void
     */
    private function maybe_run_inline_processing(string $audit_id): void {
        $inline_processing = (bool) get_option('prorank_audit_inline_processing', false);

        if (!$inline_processing) {
            $last_progress = get_option('prorank_audit_last_progress', []);
            $last_checked = isset($last_progress['checked']) ? (int) $last_progress['checked'] : 0;
            $last_time = isset($last_progress['time']) ? (int) $last_progress['time'] : 0;

            if ($last_time > 0 && (time() - $last_time) > 30 && $last_checked === 0) {
                $inline_processing = true;
                update_option('prorank_audit_inline_processing', true, false);
            }
        }

        if (!$inline_processing) {
            return;
        }

        $this->process_inline_batch($audit_id, 5);
    }

    /**
     * Process a small batch of URLs inline to show progress immediately.
     *
     * @param string $audit_id
     * @param int $limit
     * @return void
     */
    private function process_inline_batch(string $audit_id, int $limit = 5): void {
        global $wpdb;
        $table_urls = $wpdb->prefix . 'prorank_audit_urls';

        $urls = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM {$table_urls}
            WHERE audit_id = %s AND status IN ('pending', 'crawled')
            ORDER BY depth ASC
            LIMIT %d",
            $audit_id,
            $limit
        ));

        if (empty($urls)) {
            $remaining = (int) $wpdb->get_var($wpdb->prepare(
                "SELECT COUNT(*) FROM {$table_urls}
                WHERE audit_id = %s AND status IN ('pending', 'crawled')",
                $audit_id
            ));

            if ($remaining === 0) {
                $this->simple_complete_audit($audit_id);
            }

            return;
        }

        update_option('prorank_audit_state', self::STATE_CHECKING);

        foreach ($urls as $url_row) {
            if ($url_row->status === 'pending') {
                $wpdb->update(
                    $table_urls,
                    ['status' => 'crawled'],
                    ['id' => $url_row->id],
                    ['%s'],
                    ['%d']
                );
            }

            $this->simple_check_url($audit_id, (int) $url_row->id, $url_row->url);
        }
    }
    
    /**
     * Simple check URL implementation
     *
     * @param string $audit_id
     * @param int $url_id
     * @param string $url
     * @return void
     */
    private function simple_check_url(string $audit_id, int $url_id, string $url): void {
        // Get enabled check types from settings
        $saved_check_types = get_option('prorank_audit_check_types', []);
        $default_checks = [
            'meta_tags' => true,
            'https_status' => true,
            'image_optimization' => true,
            'headings_structure' => true,
            'broken_links' => true,
            'internal_linking' => true,
            'external_links' => true,
            'schema_validation' => true,
            'page_speed' => true,
            'accessibility' => true,
            'open_graph' => true,
            'twitter_cards' => true,
        ];
        $checks = array_merge($default_checks, $saved_check_types);

        // Get page content with optimized settings
        $response = wp_remote_get($url, [
            'timeout' => 3, // Reduced timeout for faster processing
            'redirection' => 2, // Limit redirects
            'user-agent' => 'ProRank SEO Site Audit/3.0',
            'sslverify' => true, // Skip SSL verification for speed
            'compress' => true, // Enable compression
        ]);

        if (is_wp_error($response)) {
            $this->add_issue($audit_id, $url, 'unreachable',
                'URL is unreachable: ' . $response->get_error_message(), 'high');
            $this->mark_url_checked($url_id);
            return;
        }

        $body = wp_remote_retrieve_body($response);
        $headers = wp_remote_retrieve_headers($response);

        // Run checks conditionally based on settings
        if (!empty($checks['meta_tags']) || !empty($checks['canonical_tags'])) {
            $this->check_meta_tags($audit_id, $url, $body);
        }
        if (!empty($checks['https_status']) || !empty($checks['ssl_certificate'])) {
            $this->check_https($audit_id, $url);
        }
        if (!empty($checks['image_optimization'])) {
            $this->check_images($audit_id, $url, $body);
        }
        if (!empty($checks['headings_structure'])) {
            $this->check_headings($audit_id, $url, $body);
        }
        if (!empty($checks['broken_links']) || !empty($checks['internal_linking']) || !empty($checks['external_links'])) {
            $this->check_links($audit_id, $url, $body);
        }
        if (!empty($checks['schema_validation'])) {
            $this->check_schema($audit_id, $url, $body);
        }
        if (!empty($checks['page_speed']) || !empty($checks['core_web_vitals'])) {
            $this->check_performance($audit_id, $url, $headers);
        }
        if (!empty($checks['accessibility'])) {
            $this->check_accessibility($audit_id, $url, $body);
        }
        if (!empty($checks['open_graph']) || !empty($checks['twitter_cards'])) {
            $this->check_social_tags($audit_id, $url, $body);
        }

        // Mark URL as checked
        $this->mark_url_checked($url_id);
    }
    
    /**
     * Simple complete audit implementation
     *
     * @param string $audit_id
     * @return void
     */
    private function simple_complete_audit(string $audit_id): void {
        global $wpdb;
        $table_audits = $wpdb->prefix . 'prorank_audits';
        $table_urls = $wpdb->prefix . 'prorank_audit_urls';
        $table_issues = $wpdb->prefix . 'prorank_audit_issues';
        
        // Check if all URLs are checked
        $pending = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table_urls} 
            WHERE audit_id = %s AND status != 'completed'",
            $audit_id
        ));
        
        if ($pending > 0) {
            // Still have pending URLs, check again later
            if (function_exists('as_schedule_single_action')) {
                as_schedule_single_action(time() + 60, self::ACTION_COMPLETE_AUDIT, [$audit_id]);
            }
            return;
        }
        
        // Calculate comprehensive score
        $score = $this->calculate_audit_score($audit_id);
        
        // Get final stats
        $total_urls = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table_urls} WHERE audit_id = %s",
            $audit_id
        ));
        
        $total_issues = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table_issues} WHERE audit_id = %s AND severity IN ('critical', 'high', 'medium', 'low')",
            $audit_id
        ));
        
        $warnings = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table_issues} WHERE audit_id = %s AND severity = 'warning'",
            $audit_id
        ));
        
        $passed = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table_issues} WHERE audit_id = %s AND severity = 'passed'",
            $audit_id
        ));
        
        // Update audit
        $wpdb->update(
            $table_audits,
            [
                'status' => self::STATE_COMPLETED,
                'end_time' => current_time('mysql'),
                'score' => $score,
                'stats' => json_encode([
                    'urls_found' => $total_urls,
                    'urls_checked' => $total_urls,
                    'issues_found' => $total_issues,
                    'warnings_found' => $warnings,
                    'passed_checks' => $passed,
                ]),
            ],
            ['id' => $audit_id],
            ['%s', '%s', '%d', '%s'],
            ['%s']
        );
        
        // Update state
        update_option('prorank_audit_state', self::STATE_IDLE);
        delete_option('prorank_audit_current_id');

        // Invalidate dashboard cache so score/issues update immediately.
        delete_transient('prorank_dashboard_stats');
    }
    
    /**
     * Transition to checking phase
     *
     * @param string $audit_id
     * @return void
     */
    private function transition_to_checking(string $audit_id): void {
        update_option('prorank_audit_state', self::STATE_CHECKING);
        
        // Get all crawled URLs
        global $wpdb;
        $table_urls = $wpdb->prefix . 'prorank_audit_urls';
        
        $urls = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM {$table_urls} 
            WHERE audit_id = %s AND status = 'crawled'",
            $audit_id
        ));
        
        // Schedule URL checks
        foreach ($urls as $url) {
            if (function_exists('as_enqueue_async_action')) {
                as_enqueue_async_action(self::ACTION_CHECK_URL, [$audit_id, $url->id, $url->url]);
            }
        }
        
        // Schedule completion check
        if (function_exists('as_schedule_single_action')) {
            as_schedule_single_action(time() + 300, self::ACTION_COMPLETE_AUDIT, [$audit_id]);
        }
    }
    
    /**
     * Add initial URLs to crawl
     *
     * @param string $audit_id
     * @return void
     */
    private function add_initial_urls(string $audit_id): void {
        global $wpdb;
        $table_urls = $wpdb->prefix . 'prorank_audit_urls';

        // Get settings - use saved values instead of hardcoded limits
        $max_urls = (int) get_option('prorank_audit_max_urls', 500);

        // Calculate proportional limits based on max_urls setting
        $sitemap_limit = min(intval($max_urls * 0.5), 500);   // 50% for sitemap, max 500
        $posts_limit = min(intval($max_urls * 0.25), 200);    // 25% for posts, max 200
        $pages_limit = min(intval($max_urls * 0.25), 200);    // 25% for pages, max 200

        $urls_to_add = [
            home_url('/') => 0,
        ];

        // Add sitemap URLs (use sitemap_limit instead of hardcoded 50)
        $sitemap_url = home_url('/sitemap_index.xml');
        $sitemap_response = wp_remote_get($sitemap_url);

        if (!is_wp_error($sitemap_response)) {
            $sitemap_content = wp_remote_retrieve_body($sitemap_response);
            if (!empty($sitemap_content)) {
                // Parse sitemap
                preg_match_all('/<loc>(.*?)<\/loc>/i', $sitemap_content, $matches);
                if (!empty($matches[1])) {
                    foreach (array_slice($matches[1], 0, $sitemap_limit) as $url) {
                        $urls_to_add[$url] = 1;
                    }
                }
            }
        }

        // Add recent posts (use posts_limit instead of hardcoded 20)
        $recent_posts = get_posts([
            'numberposts' => $posts_limit,
            'post_status' => 'publish',
        ]);

        foreach ($recent_posts as $post) {
            $urls_to_add[get_permalink($post)] = 1;
        }

        // Add recent pages (use pages_limit instead of hardcoded 20)
        $recent_pages = get_pages([
            'number' => $pages_limit,
            'post_status' => 'publish',
        ]);

        foreach ($recent_pages as $page) {
            $urls_to_add[get_permalink($page)] = 1;
        }

        // Enforce max_urls limit on total URLs
        if (count($urls_to_add) > $max_urls) {
            $urls_to_add = array_slice($urls_to_add, 0, $max_urls, true);
        }

        // Insert URLs
        foreach ($urls_to_add as $url => $depth) {
            $wpdb->insert(
                $table_urls,
                [
                    'audit_id' => $audit_id,
                    'url' => $url,
                    'status' => 'pending',
                    'depth' => $depth,
                ],
                ['%s', '%s', '%s', '%d']
            );
        }
    }
    
    /**
     * Check meta tags
     *
     * @param string $audit_id
     * @param string $url
     * @param string $html
     * @return void
     */
    private function check_meta_tags(string $audit_id, string $url, string $html): void {
        // Check title tag
        if (!preg_match('/<title[^>]*>(.*?)<\/title>/i', $html, $matches)) {
            $this->add_issue($audit_id, $url, 'missing_title', 
                'Page is missing a title tag', 'critical');
        } else {
            $title = trim(wp_strip_all_tags($matches[1]));
            if (strlen($title) < 10) {
                $this->add_issue($audit_id, $url, 'short_title', 
                    'Title tag is too short (less than 10 characters)', 'medium');
            } elseif (strlen($title) > 60) {
                $this->add_issue($audit_id, $url, 'long_title', 
                    'Title tag is too long (more than 60 characters)', 'low');
            } else {
                $this->add_passed($audit_id, $url, 'title_tag', 
                    'Title tag is properly set');
            }
        }
        
        // Check meta description
        if (!preg_match('/<meta[^>]+name=[\'"]description[\'"][^>]+content=[\'"]([^\'"]*)[\'"]/i', $html, $matches)) {
            $this->add_issue($audit_id, $url, 'missing_meta_description', 
                'Page is missing a meta description', 'high');
        } else {
            $description = trim($matches[1]);
            if (strlen($description) < 50) {
                $this->add_issue($audit_id, $url, 'short_meta_description', 
                    'Meta description is too short (less than 50 characters)', 'medium');
            } elseif (strlen($description) > 160) {
                $this->add_issue($audit_id, $url, 'long_meta_description', 
                    'Meta description is too long (more than 160 characters)', 'low');
            } else {
                $this->add_passed($audit_id, $url, 'meta_description', 
                    'Meta description is properly set');
            }
        }
        
        // Check canonical tag
        if (preg_match('/<link[^>]+rel=[\'"]canonical[\'"][^>]+href=[\'"]([^\'"]*)[\'"]/i', $html, $matches)) {
            $this->add_passed($audit_id, $url, 'canonical_tag', 
                'Canonical tag is present');
        } else {
            $this->add_warning($audit_id, $url, 'missing_canonical', 
                'Page is missing a canonical tag');
        }
        
        // Check robots meta
        if (preg_match('/<meta[^>]+name=[\'"]robots[\'"][^>]+content=[\'"]([^\'"]*)[\'"]/i', $html, $matches)) {
            $robots = strtolower($matches[1]);
            if (strpos($robots, 'noindex') !== false) {
                $this->add_warning($audit_id, $url, 'noindex', 
                    'Page has noindex directive');
            }
        }
    }
    
    /**
     * Check HTTPS
     *
     * @param string $audit_id
     * @param string $url
     * @return void
     */
    private function check_https(string $audit_id, string $url): void {
        if (strpos($url, 'https://') !== 0) {
            $this->add_issue($audit_id, $url, 'no_https', 
                'Page is not served over HTTPS', 'critical');
        } else {
            $this->add_passed($audit_id, $url, 'https', 
                'Page is served over HTTPS');
        }
    }
    
    /**
     * Check images
     *
     * @param string $audit_id
     * @param string $url
     * @param string $html
     * @return void
     */
    private function check_images(string $audit_id, string $url, string $html): void {
        preg_match_all('/<img[^>]+>/i', $html, $images);
        
        $missing_alt_count = 0;
        $large_images = 0;
        
        foreach ($images[0] as $img) {
            // Check for alt text
            if (!preg_match('/alt=[\'"]([^\'"]*)[\'"]/', $img, $alt_match) || empty(trim($alt_match[1]))) {
                $missing_alt_count++;
            }
            
            // Check image size (simplified check)
            if (preg_match('/width=[\'"](\d+)[\'"]/', $img, $width_match) && 
                preg_match('/height=[\'"](\d+)[\'"]/', $img, $height_match)) {
                $width = (int) $width_match[1];
                $height = (int) $height_match[1];
                if ($width > 1920 || $height > 1080) {
                    $large_images++;
                }
            }
        }
        
        if ($missing_alt_count > 0) {
            $this->add_issue($audit_id, $url, 'missing_alt_text', 
                "$missing_alt_count images missing alt text", 'medium');
        }
        
        if ($large_images > 0) {
            $this->add_issue($audit_id, $url, 'large_images', 
                "$large_images oversized images found", 'medium');
        }
        
        if ($missing_alt_count === 0 && count($images[0]) > 0) {
            $this->add_passed($audit_id, $url, 'image_alt_text', 
                'All images have alt text');
        }
    }
    
    /**
     * Check headings
     *
     * @param string $audit_id
     * @param string $url
     * @param string $html
     * @return void
     */
    private function check_headings(string $audit_id, string $url, string $html): void {
        // Check for H1
        preg_match_all('/<h1[^>]*>(.*?)<\/h1>/i', $html, $h1_matches);
        
        if (count($h1_matches[0]) === 0) {
            $this->add_issue($audit_id, $url, 'missing_h1', 
                'Page is missing an H1 tag', 'high');
        } elseif (count($h1_matches[0]) > 1) {
            $this->add_issue($audit_id, $url, 'multiple_h1', 
                'Page has multiple H1 tags', 'medium');
        } else {
            $this->add_passed($audit_id, $url, 'h1_tag', 
                'Page has exactly one H1 tag');
        }
        
        // Check heading hierarchy
        preg_match_all('/<h([1-6])[^>]*>/i', $html, $all_headings, PREG_OFFSET_CAPTURE);
        
        if (!empty($all_headings[1])) {
            $previous_level = 0;
            $hierarchy_broken = false;
            
            foreach ($all_headings[1] as $heading) {
                $level = (int) $heading[0];
                if ($level > $previous_level + 1 && $previous_level > 0) {
                    $hierarchy_broken = true;
                    break;
                }
                $previous_level = $level;
            }
            
            if ($hierarchy_broken) {
                $this->add_warning($audit_id, $url, 'heading_hierarchy', 
                    'Heading hierarchy is broken (skipped levels)');
            } else {
                $this->add_passed($audit_id, $url, 'heading_hierarchy', 
                    'Heading hierarchy is properly structured');
            }
        }
    }
    
    /**
     * Check links
     *
     * @param string $audit_id
     * @param string $url
     * @param string $html
     * @return void
     */
    private function check_links(string $audit_id, string $url, string $html): void {
        preg_match_all('/<a[^>]+href=[\'"]([^\'"\#]+)[\'"][^>]*>/i', $html, $links);
        
        $internal_links = 0;
        $external_links = 0;
        $broken_links = [];
        
        $home_url = home_url();
        
        foreach ($links[1] as $link) {
            if (strpos($link, 'http') === 0) {
                if (strpos($link, $home_url) === 0) {
                    $internal_links++;
                } else {
                    $external_links++;
                }
            } else {
                // Relative URL
                $internal_links++;
            }
        }
        
        if ($internal_links === 0) {
            $this->add_warning($audit_id, $url, 'no_internal_links', 
                'Page has no internal links');
        } else {
            $this->add_passed($audit_id, $url, 'internal_links', 
                "Page has $internal_links internal links");
        }
    }
    
    /**
     * Check schema markup
     *
     * @param string $audit_id
     * @param string $url
     * @param string $html
     * @return void
     */
    private function check_schema(string $audit_id, string $url, string $html): void {
        // Check for JSON-LD
        if (preg_match('/<script[^>]+type=[\'"]application\/ld\+json[\'"][^>]*>/i', $html)) {
            $this->add_passed($audit_id, $url, 'schema_markup', 
                'Page has JSON-LD schema markup');
        } else {
            $this->add_warning($audit_id, $url, 'no_schema', 
                'Page is missing schema markup');
        }
    }
    
    /**
     * Check performance indicators
     *
     * @param string $audit_id
     * @param string $url
     * @param mixed $headers
     * @return void
     */
    private function check_performance(string $audit_id, string $url, $headers): void {
        // Check compression
        $encoding = '';
        if (is_array($headers)) {
            $encoding = $headers['content-encoding'] ?? '';
        } elseif (is_object($headers)) {
            $encoding = $headers->getValues('content-encoding')[0] ?? '';
        }
        
        if (empty($encoding) || strpos($encoding, 'gzip') === false) {
            $this->add_issue($audit_id, $url, 'no_compression', 
                'Page is not compressed with GZIP', 'medium');
        } else {
            $this->add_passed($audit_id, $url, 'gzip_compression', 
                'Page is compressed with GZIP');
        }
        
        // Check caching headers
        $cache_control = '';
        if (is_array($headers)) {
            $cache_control = $headers['cache-control'] ?? '';
        } elseif (is_object($headers)) {
            $cache_control = $headers->getValues('cache-control')[0] ?? '';
        }
        
        if (empty($cache_control) || strpos($cache_control, 'no-cache') !== false) {
            $this->add_issue($audit_id, $url, 'no_caching', 
                'Page has no caching headers', 'low');
        } else {
            $this->add_passed($audit_id, $url, 'caching_headers', 
                'Page has proper caching headers');
        }
    }
    
    /**
     * Check accessibility
     *
     * @param string $audit_id
     * @param string $url
     * @param string $html
     * @return void
     */
    private function check_accessibility(string $audit_id, string $url, string $html): void {
        // Check lang attribute
        if (!preg_match('/<html[^>]+lang=[\'"]([^\'"]*)[\'"]/i', $html)) {
            $this->add_issue($audit_id, $url, 'missing_lang', 
                'HTML element is missing lang attribute', 'high');
        } else {
            $this->add_passed($audit_id, $url, 'lang_attribute', 
                'HTML has lang attribute');
        }
        
        // Check viewport meta
        if (!preg_match('/<meta[^>]+name=[\'"]viewport[\'"]/i', $html)) {
            $this->add_issue($audit_id, $url, 'missing_viewport', 
                'Page is missing viewport meta tag', 'high');
        } else {
            $this->add_passed($audit_id, $url, 'viewport_meta', 
                'Page has viewport meta tag');
        }
    }
    
    /**
     * Check social tags
     *
     * @param string $audit_id
     * @param string $url
     * @param string $html
     * @return void
     */
    private function check_social_tags(string $audit_id, string $url, string $html): void {
        // Check Open Graph tags
        $og_tags = ['og:title', 'og:description', 'og:image', 'og:url'];
        $found_og = 0;
        
        foreach ($og_tags as $tag) {
            if (preg_match('/<meta[^>]+property=[\'"]' . $tag . '[\'"]/i', $html)) {
                $found_og++;
            }
        }
        
        if ($found_og === 0) {
            $this->add_warning($audit_id, $url, 'no_open_graph', 
                'Page is missing Open Graph tags');
        } elseif ($found_og < count($og_tags)) {
            $this->add_warning($audit_id, $url, 'incomplete_open_graph', 
                'Page has incomplete Open Graph tags');
        } else {
            $this->add_passed($audit_id, $url, 'open_graph', 
                'Page has complete Open Graph tags');
        }
        
        // Check Twitter Card
        if (preg_match('/<meta[^>]+name=[\'"]twitter:card[\'"]/i', $html)) {
            $this->add_passed($audit_id, $url, 'twitter_card', 
                'Page has Twitter Card meta tags');
        } else {
            $this->add_warning($audit_id, $url, 'no_twitter_card', 
                'Page is missing Twitter Card meta tags');
        }
    }
    
    /**
     * Add an issue
     *
     * @param string $audit_id
     * @param string $url
     * @param string $type
     * @param string $message
     * @param string $severity
     * @return void
     */
    private function add_issue(string $audit_id, string $url, string $type, string $message, string $severity): void {
        global $wpdb;
        $table_issues = $wpdb->prefix . 'prorank_audit_issues';
        
        $wpdb->insert(
            $table_issues,
            [
                'audit_id' => $audit_id,
                'url' => $url,
                'type' => $type,
                'message' => $message,
                'severity' => $severity,
                'data' => json_encode([]),
            ],
            ['%s', '%s', '%s', '%s', '%s', '%s']
        );
    }
    
    /**
     * Add a warning
     *
     * @param string $audit_id
     * @param string $url
     * @param string $type
     * @param string $message
     * @return void
     */
    private function add_warning(string $audit_id, string $url, string $type, string $message): void {
        $this->add_issue($audit_id, $url, $type, $message, 'warning');
    }
    
    /**
     * Add a passed check
     *
     * @param string $audit_id
     * @param string $url
     * @param string $type
     * @param string $message
     * @return void
     */
    private function add_passed(string $audit_id, string $url, string $type, string $message): void {
        $this->add_issue($audit_id, $url, $type, $message, 'passed');
    }
    
    /**
     * Mark URL as checked
     *
     * @param int $url_id
     * @return void
     */
    private function mark_url_checked(int $url_id): void {
        global $wpdb;
        $table_urls = $wpdb->prefix . 'prorank_audit_urls';
        
        $wpdb->update(
            $table_urls,
            ['status' => 'completed'],
            ['id' => $url_id],
            ['%s'],
            ['%d']
        );
    }
    
    /**
     * Calculate audit score
     *
     * @param string $audit_id
     * @return int
     */
    private function calculate_audit_score(string $audit_id): int {
        global $wpdb;
        $table_issues = $wpdb->prefix . 'prorank_audit_issues';

        $passed = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table_issues} WHERE audit_id = %s AND severity = 'passed'",
            $audit_id
        ));

        $score_rows = $wpdb->get_results($wpdb->prepare(
            "SELECT type, severity, message
            FROM {$table_issues}
            WHERE audit_id = %s AND severity != 'passed'
            GROUP BY type, severity, message",
            $audit_id
        ));

        $weighted_counts = [
            'critical' => 0.0,
            'high' => 0.0,
            'medium' => 0.0,
            'low' => 0.0,
            'warning' => 0.0,
        ];

        $total_issue_groups = 0;
        if ($score_rows && !is_wp_error($score_rows)) {
            foreach ($score_rows as $score_row) {
                $severity = strtolower((string) ($score_row->severity ?? ''));
                if (!isset($weighted_counts[$severity])) {
                    continue;
                }

                $weighted_counts[$severity] += $this->get_site_audit_issue_score_multiplier((string) ($score_row->type ?? ''));
                $total_issue_groups++;
            }
        }

        $total_checks = $passed + $total_issue_groups;
        if ($total_checks === 0) {
            return 100;
        }

        $score = 100;
        $score -= min(30, $weighted_counts['critical'] * 6.0);
        $score -= min(24, $weighted_counts['high'] * 3.0);
        $score -= min(14, $weighted_counts['medium'] * 1.2);
        $score -= min(8, $weighted_counts['low'] * 0.4);
        $score -= min(4, $weighted_counts['warning'] * 0.15);

        $pass_rate = $passed / max(1, $total_checks);
        $score += min(5, round($pass_rate * 5));

        return max(0, min(100, round($score)));
    }

    /**
     * Normalize issue types for scoring aliases.
     */
    private function get_site_audit_issue_score_key(string $type): string {
        $normalized = strtolower(trim($type));
        $aliases = [
            'short_title' => 'title_too_short',
            'long_title' => 'title_too_long',
            'short_meta_description' => 'meta_desc_too_short',
            'long_meta_description' => 'meta_desc_too_long',
            'multiple_h1' => 'multiple_h1',
            'duplicate_h1' => 'multiple_h1',
            'missing_h1' => 'missing_h1',
            'heading_hierarchy' => 'heading_skip_levels',
            'missing_alt_text' => 'missing_alt_text',
            'missing_lang_attribute' => 'missing_lang',
            'missing_viewport' => 'missing_viewport',
            'cwv_not_measured' => 'cwv_not_measured',
        ];

        return $aliases[$normalized] ?? $normalized;
    }

    /**
     * Determine whether an issue is a site-wide maintenance/environment check.
     */
    private function is_site_audit_sitewide_maintenance_issue(string $type): bool {
        $key = $this->get_site_audit_issue_score_key($type);

        $sitewide_types = [
            'plugins_need_updates',
            'potential_plugin_conflict',
            'wordpress_outdated',
            'php_outdated',
            'too_many_plugins',
            'large_autoload',
            'moderate_autoload',
            'many_revisions',
            'database_optimization',
        ];

        return in_array($key, $sitewide_types, true);
    }

    /**
     * Determine whether an issue is a crawler/server transport failure.
     */
    private function is_site_audit_crawl_server_issue(string $type): bool {
        $key = $this->get_site_audit_issue_score_key($type);

        if (in_array($key, ['connectivity', 'crawl_fetch_failed', 'crawl_timeout', 'crawl_error', 'server_response_error'], true)) {
            return true;
        }

        return strpos($key, 'crawl_') === 0;
    }

    /**
     * Scope multiplier so maintenance and transport failures do not score like page SEO failures.
     */
    private function get_site_audit_issue_scope_multiplier(string $type): float {
        if ($this->is_site_audit_sitewide_maintenance_issue($type)) {
            return 0.35;
        }

        if ($this->is_site_audit_crawl_server_issue($type)) {
            return 0.45;
        }

        return 1.0;
    }

    /**
     * Return score multiplier for an issue type.
     */
    private function get_site_audit_issue_score_multiplier(string $type): float {
        $key = $this->get_site_audit_issue_score_key($type);
        $non_scoring = [
            'cwv_not_measured',
        ];

        if (in_array($key, $non_scoring, true)) {
            return 0.0;
        }

        $multipliers = [
            'title_too_short' => 0.15,
            'title_too_long' => 0.15,
            'meta_desc_too_short' => 0.10,
            'meta_desc_too_long' => 0.10,
            'multiple_h1' => 0.20,
            'heading_skip_levels' => 0.15,
        ];

        return ($multipliers[$key] ?? 1.0) * $this->get_site_audit_issue_scope_multiplier($type);
    }

    /**
     * Calculate a weighted deduction for a single issue.
     */
    private function calculate_site_audit_issue_score_deduction(string $type, string $severity): float {
        $severity = strtolower(trim($severity));
        $base = [
            'critical' => 6.0,
            'high' => 3.0,
            'medium' => 1.2,
            'low' => 0.4,
            'warning' => 0.15,
        ];

        if (!isset($base[$severity])) {
            return 0.0;
        }

        return $base[$severity] * $this->get_site_audit_issue_score_multiplier($type);
    }
    
    /**
     * Get issue category
     *
     * @param string $type
     * @return string
     */
    private function get_issue_category(string $type): string {
        foreach ($this->get_site_audit_category_definitions() as $category_key => $definition) {
            if (in_array($type, $definition['types'], true)) {
                return $category_key;
            }
        }

        if (strpos($type, 'missing_security_header') === 0) {
            return 'security';
        }

        if (
            strpos($type, 'cwv_') === 0 ||
            strpos($type, 'core_web_vitals_') === 0 ||
            preg_match('/^(lcp|cls|inp|fcp|tti|tbt|speed_index)_/i', $type)
        ) {
            return 'performance';
        }

        return 'content';
    }
    
    /**
     * Check if issue can be auto-fixed
     *
     * @param string $type
     * @return bool
     */
    private function can_autofix(string $type): bool {
        if (!(bool) get_option('prorank_audit_enable_autofix', false)) {
            return false;
        }

        $option_by_issue_type = [
            'missing_meta_description' => 'prorank_audit_autofix_meta_descriptions',
            'missing_alt_text' => 'prorank_audit_autofix_alt_texts',
            'no_compression' => 'prorank_audit_autofix_compression',
            'no_caching' => 'prorank_audit_autofix_caching',
        ];

        if (isset($option_by_issue_type[$type])) {
            return (bool) get_option($option_by_issue_type[$type], true);
        }

        $autofixable = [
            'missing_meta_description',
            'missing_alt_text',
            'no_compression',
            'no_caching',
        ];
        
        return in_array($type, $autofixable, true);
    }
    
    /**
     * Apply auto-fix
     *
     * @param string $type
     * @param int $issue_id
     * @return bool
     */
    private function apply_autofix(string $type, int $issue_id): bool {
        switch ($type) {
            case 'no_compression':
                return $this->enable_gzip_compression();
                
            case 'no_caching':
                return $this->enable_browser_caching();
                
            case 'missing_meta_description':
                return $this->generate_meta_description($issue_id);
                
            case 'missing_alt_text':
                return $this->generate_alt_text($issue_id);
                
            default:
                return false;
        }
    }
    
    /**
     * Enable GZIP compression
     *
     * @return bool
     */
    private function enable_gzip_compression(): bool {
        $htaccess_path = prorank_home_file_path('.htaccess');
        
        if (!wp_is_writable($htaccess_path)) {
            return false;
        }
        
        $gzip_rules = "\n# BEGIN ProRank SEO GZIP Compression\n";
        $gzip_rules .= "<IfModule mod_deflate.c>\n";
        $gzip_rules .= "  AddOutputFilterByType DEFLATE text/plain\n";
        $gzip_rules .= "  AddOutputFilterByType DEFLATE text/html\n";
        $gzip_rules .= "  AddOutputFilterByType DEFLATE text/xml\n";
        $gzip_rules .= "  AddOutputFilterByType DEFLATE text/css\n";
        $gzip_rules .= "  AddOutputFilterByType DEFLATE application/xml\n";
        $gzip_rules .= "  AddOutputFilterByType DEFLATE application/xhtml+xml\n";
        $gzip_rules .= "  AddOutputFilterByType DEFLATE application/rss+xml\n";
        $gzip_rules .= "  AddOutputFilterByType DEFLATE application/javascript\n";
        $gzip_rules .= "  AddOutputFilterByType DEFLATE application/x-javascript\n";
        $gzip_rules .= "</IfModule>\n";
        $gzip_rules .= "# END ProRank SEO GZIP Compression\n";
        
        $htaccess_content = file_get_contents($htaccess_path);
        
        if (strpos($htaccess_content, '# BEGIN ProRank SEO GZIP Compression') === false) {
            $htaccess_content = $gzip_rules . $htaccess_content;
            return file_put_contents($htaccess_path, $htaccess_content) !== false;
        }
        
        return true;
    }
    
    /**
     * Enable browser caching
     *
     * @return bool
     */
    private function enable_browser_caching(): bool {
        $htaccess_path = prorank_home_file_path('.htaccess');
        
        if (!wp_is_writable($htaccess_path)) {
            return false;
        }
        
        $cache_rules = "\n# BEGIN ProRank SEO Browser Caching\n";
        $cache_rules .= "<IfModule mod_expires.c>\n";
        $cache_rules .= "  ExpiresActive On\n";
        $cache_rules .= "  ExpiresByType image/jpg \"access plus 1 year\"\n";
        $cache_rules .= "  ExpiresByType image/jpeg \"access plus 1 year\"\n";
        $cache_rules .= "  ExpiresByType image/gif \"access plus 1 year\"\n";
        $cache_rules .= "  ExpiresByType image/png \"access plus 1 year\"\n";
        $cache_rules .= "  ExpiresByType text/css \"access plus 1 month\"\n";
        $cache_rules .= "  ExpiresByType application/javascript \"access plus 1 month\"\n";
        $cache_rules .= "</IfModule>\n";
        $cache_rules .= "# END ProRank SEO Browser Caching\n";
        
        $htaccess_content = file_get_contents($htaccess_path);
        
        if (strpos($htaccess_content, '# BEGIN ProRank SEO Browser Caching') === false) {
            $htaccess_content = $cache_rules . $htaccess_content;
            return file_put_contents($htaccess_path, $htaccess_content) !== false;
        }
        
        return true;
    }
    
    /**
     * Generate meta description
     *
     * @param int $issue_id
     * @return bool
     */
    private function generate_meta_description(int $issue_id): bool {
        // This would integrate with the content module to generate descriptions
        // For now, return false as it requires post/page integration
        return false;
    }
    
    /**
     * Generate alt text
     *
     * @param int $issue_id
     * @return bool
     */
    private function generate_alt_text(int $issue_id): bool {
        // This would integrate with the media module to generate alt text
        // For now, return false as it requires media library integration
        return false;
    }
    
    /**
     * Get fix title
     *
     * @param string $type
     * @return string
     */
    private function get_fix_title(string $type): string {
        $titles = [
            'no_compression' => __('Enable GZIP Compression', 'prorank-seo'),
            'no_caching' => __('Enable Browser Caching', 'prorank-seo'),
            'missing_meta_description' => __('Generate Meta Descriptions', 'prorank-seo'),
            'missing_alt_text' => __('Generate Alt Text', 'prorank-seo'),
        ];
        
        return $titles[$type] ?? $type;
    }
    
    /**
     * Get fix description
     *
     * @param string $type
     * @return string
     */
    private function get_fix_description(string $type): string {
        $descriptions = [
            'no_compression' => __('Compress your pages to reduce file size and improve loading speed', 'prorank-seo'),
            'no_caching' => __('Set browser caching headers to improve repeat visitor performance', 'prorank-seo'),
            'missing_meta_description' => __('Automatically generate meta descriptions from page content', 'prorank-seo'),
            'missing_alt_text' => __('Generate descriptive alt text for images', 'prorank-seo'),
        ];
        
        return $descriptions[$type] ?? '';
    }
    
    /**
     * Get fix impact
     *
     * @param string $type
     * @return string
     */
    private function get_fix_impact(string $type): string {
        $impacts = [
            'no_compression' => __('Can reduce page size by 70-90%', 'prorank-seo'),
            'no_caching' => __('Improves repeat visitor load time by 50%+', 'prorank-seo'),
            'missing_meta_description' => __('Improves click-through rates from search results', 'prorank-seo'),
            'missing_alt_text' => __('Improves accessibility and image SEO', 'prorank-seo'),
        ];
        
        return $impacts[$type] ?? '';
    }
    
    /**
     * Get empty categories
     *
     * @return array
     */
    private function get_empty_categories(): array {
        $categories = [];

        foreach ($this->get_site_audit_category_definitions() as $category_key => $definition) {
            $categories[$category_key] = [
                'name' => $definition['name'],
                'score' => 100,
                'total' => 0,
                'passed' => 0,
                'critical' => 0,
                'high' => 0,
                'medium' => 0,
                'low' => 0,
                'warning' => 0,
                'warnings' => 0,
                'instances' => 0,
                'penalty' => 0,
            ];
        }

        return $categories;
    }
    
    /**
     * Get category stats
     *
     * @param string $audit_id
     * @return array
     */
    private function get_category_stats(string $audit_id): array {
        global $wpdb;
        $table_issues = $wpdb->prefix . 'prorank_audit_issues';
        
        $categories = $this->get_empty_categories();
        
        // Use grouped findings rather than raw issue rows so the dashboard reflects
        // distinct audit findings instead of massively inflated per-URL instances.
        $issues = $wpdb->get_results($wpdb->prepare(
            "SELECT type, severity, message, COUNT(*) as count
            FROM {$table_issues} 
            WHERE audit_id = %s 
            GROUP BY type, severity, message",
            $audit_id
        ));
        
        // Check if we got results
        if (!$issues || is_wp_error($issues)) {
            return $categories;
        }
        
        // Categorize issues
        foreach ($issues as $issue) {
            $category = $this->get_issue_category($issue->type);
            
            if (isset($categories[$category])) {
                $categories[$category]['instances'] += (int) $issue->count;

                if (array_key_exists($issue->severity, $categories[$category])) {
                    $categories[$category][$issue->severity] += 1;
                    if ($issue->severity === 'warning') {
                        $categories[$category]['warnings'] = $categories[$category]['warning'];
                    }
                }

                $categories[$category]['penalty'] += $this->calculate_site_audit_issue_score_deduction(
                    (string) $issue->type,
                    (string) $issue->severity
                );
            }
        }

        foreach ($categories as $category_key => $stats) {
            $total = $stats['critical'] + $stats['high'] + $stats['medium'] + $stats['low'] + $stats['warning'] + $stats['passed'];
            $categories[$category_key]['total'] = $total;
            $categories[$category_key]['warnings'] = $stats['warning'];
            $categories[$category_key]['score'] = $this->calculate_category_score($categories[$category_key]);
        }

        return $categories;
    }

    /**
     * Get category definitions used by the dashboard and history views.
     *
     * @return array<string,array{name:string,types:array<int,string>}>
     */
    private function get_site_audit_category_definitions(): array {
        return [
            'technical-seo' => [
                'name' => __('Technical SEO', 'prorank-seo'),
                'types' => [
                    'has_title',
                    'missing_title',
                    'duplicate_title',
                    'empty_title',
                    'has_canonical',
                    'missing_canonical',
                    'duplicate_canonical',
                    'multiple_canonicals',
                    'http_status',
                    'connectivity',
                    'redirect',
                    'indexable',
                    'noindex',
                    'noindex_set',
                    'robots_noindex',
                    'missing_sitemap',
                    'missing_sitemap_reference',
                    'missing_robots_txt',
                    'has_valid_sitemap',
                    'missing_lang_attribute',
                    'has_lang_attribute',
                    'missing_viewport',
                    'has_viewport_meta',
                ],
            ],
            'on-page-seo' => [
                'name' => __('On-Page SEO', 'prorank-seo'),
                'types' => [
                    'short_title',
                    'long_title',
                    'title_length_optimal',
                    'title_keyword_stuffing',
                    'missing_meta_description',
                    'empty_meta_description',
                    'short_meta_description',
                    'long_meta_description',
                    'meta_description_optimal',
                    'missing_h1',
                    'multiple_h1',
                    'duplicate_h1',
                    'has_single_h1',
                    'heading_hierarchy',
                    'missing_alt_text',
                    'missing_og_tag',
                    'missing_og_tags',
                    'missing_twitter_cards',
                    'no_keyword_stuffing',
                    'keyword_optimization',
                    'has_schema_markup',
                    'missing_schema',
                ],
            ],
            'performance' => [
                'name' => __('Performance', 'prorank-seo'),
                'types' => [
                    'cache_headers_set',
                    'no_cache_headers',
                    'gzip_enabled',
                    'no_compression',
                    'fast_load_time',
                    'slow_load_time',
                    'moderate_load_time',
                    'optimal_page_size',
                    'large_page_size',
                    'broken_image',
                    'broken_images',
                    'unoptimized_images',
                    'render_blocking_resources',
                    'large_dom_size',
                    'cwv_not_measured',
                ],
            ],
            'security' => [
                'name' => __('Security', 'prorank-seo'),
                'types' => [
                    'https_enabled',
                    'ssl_enabled',
                    'missing_https',
                    'mixed_content',
                    'no_mixed_content',
                    'php_up_to_date',
                    'wordpress_up_to_date',
                    'plugins_need_updates',
                ],
            ],
            'content' => [
                'name' => __('Content', 'prorank-seo'),
                'types' => [
                    'adequate_content_length',
                    'good_content_length',
                    'short_content',
                    'thin_content',
                    'very_thin_content',
                    'duplicate_content',
                    'unique_content',
                    'poor_readability',
                    'long_sentences',
                    'missing_headings',
                    'orphaned_page',
                    'has_internal_links',
                    'no_internal_links',
                    'broken_link',
                    'broken_links',
                    'broken_internal_links',
                    'external_broken_links',
                ],
            ],
        ];
    }

    /**
     * Calculate a category score aligned with the audit dashboard.
     *
     * @param array<string,int|string> $stats
     * @return int
     */
    private function calculate_category_score(array $stats): int {
        $total = (int) ($stats['total'] ?? 0);

        if ($total === 0) {
            return 100;
        }

        $penalty = (float) ($stats['penalty'] ?? 0);

        return max(0, min(100, 100 - (int) round($penalty)));
    }
    
    /**
     * Clear scheduled events for a specific audit
     *
     * @param string $audit_id
     * @return void
     */
    private function clear_audit_scheduled_events(string $audit_id): void {
        // Clear specific audit events
        $hooks = [
            'prorank_crawl_batch',
            'prorank_check_url', 
            'prorank_complete_audit',
            'prorank_schedule_url_checks',
            'prorank_check_audit_url',
            'prorank_run_site_crawler',
        ];
        
        foreach ($hooks as $hook) {
            $timestamp = wp_next_scheduled($hook, [$audit_id]);
            if ($timestamp) {
                wp_unschedule_event($timestamp, $hook, [$audit_id]);
            }
        }
        
        // Clear all URL check events for this audit
        $crons = _get_cron_array();
        foreach ($crons as $timestamp => $cron) {
            foreach ($cron as $hook => $args) {
                if (strpos($hook, 'prorank_') === 0 && strpos($hook, 'audit') !== false) {
                    foreach ($args as $key => $arg) {
                        if (isset($arg['args'][0]) && $arg['args'][0] === $audit_id) {
                            wp_unschedule_event($timestamp, $hook, $arg['args']);
                        }
                    }
                }
            }
        }
    }
    
    /**
     * Clear all audit-related cron events
     *
     * @return void
     */
        /**
     * Clear all audit-related cron events
     */
    private function clear_all_audit_crons(): void {
        $crons = _get_cron_array();
        $cleared = 0;
        foreach ($crons as $timestamp => $cron) {
            foreach ($cron as $hook => $args) {
                if (strpos($hook, 'prorank_') === 0 && (strpos($hook, 'audit') !== false || strpos($hook, 'crawl') !== false || strpos($hook, 'check') !== false)) {
                    foreach ($args as $arg) {
                        wp_unschedule_event($timestamp, $hook, $arg['args']);
                        $cleared++;
                    }
                }
            }
        }
        if ($cleared > 0) {
            prorank_log('[ProRank SEO] Cleared ' . $cleared . ' audit-related cron events');
        }
    }

    /**
     * Send alert email when audit completes
     */
    private function maybe_send_alert(?string $audit_id = null): void {
        try {
            if (!(bool) get_option('prorank_audit_email_notifications', true)) {
                return;
            }

            $status = $this->rest_get_status();
            if (!is_array($status) || empty($status['state']) || $status['state'] !== 'completed') {
                return;
            }
            $score = $status['score'] ?? 0;
            $critical = $status['stats']['critical_issues'] ?? 0;
            $high = $status['stats']['high_issues'] ?? 0;
            $critical_only = (bool) get_option('prorank_audit_email_critical_only', false);
            if ($critical_only && (int) $critical <= 0) {
                return;
            }
            if (!$critical_only && $score >= 90 && $critical == 0 && $high == 0) {
                return;
            }
            $to = $this->get_audit_notification_recipients();
            if (empty($to)) {
                return;
            }
/* translators: %1$: value */
            $subject = sprintf(__('Site Audit results: Score %1$d', 'prorank-seo'), $score);
            $body = "Site Audit completed

";
            $body .= 'Score: ' . $score . "
";
            $body .= 'Critical issues: ' . $critical . "
";
            $body .= 'High issues: ' . $high . "
";
            $body .= 'Audit ID: ' . ($status['audit_id'] ?? '') . "
";
            $body .= 'View audit in WP Admin.';
            wp_mail($to, $subject, $body);
        } catch (\Exception $e) {
            // ignore
        }
    }


    /**
     * Schedule recurring audit if enabled
     */
    private function maybe_schedule_recurring_audit(): void {
        $enabled = (bool) get_option('prorank_audit_schedule_enabled', false);
        $frequency = sanitize_key((string) get_option('prorank_audit_schedule_frequency', 'weekly'));
        $recurrence_map = [
            'daily' => 'daily',
            'weekly' => 'weekly',
            'biweekly' => 'prorank_biweekly',
            'monthly' => 'monthly',
        ];
        $recurrence = $recurrence_map[$frequency] ?? 'weekly';
        $schedule_time = (string) get_option('prorank_audit_schedule_time', '02:00');
        if (!preg_match('/^(?:[01]\d|2[0-3]):[0-5]\d$/', $schedule_time)) {
            $schedule_time = '02:00';
        }

        $hook = 'prorank_audit_scheduled_run';
        $existing = wp_next_scheduled($hook);
        $event = function_exists('wp_get_scheduled_event')
            ? wp_get_scheduled_event($hook)
            : null;

        $schedule_exists = wp_get_schedules();
        if (!isset($schedule_exists[$recurrence])) {
            $recurrence = $frequency === 'daily' ? 'daily' : 'weekly';
        }

        if ($enabled) {
            $needs_reschedule = !$existing;

            if ($event && isset($event->schedule) && $event->schedule !== $recurrence) {
                $needs_reschedule = true;
            }

            if ($existing && wp_date('H:i', (int) $existing) !== $schedule_time) {
                $needs_reschedule = true;
            }

            if ($needs_reschedule) {
                wp_clear_scheduled_hook($hook);
                wp_schedule_event($this->get_next_schedule_timestamp($schedule_time), $recurrence, $hook);
            }
        } else {
            wp_clear_scheduled_hook($hook);
        }
    }

    /**
     * Add custom recurrence intervals for Site Audit schedules.
     *
     * @param array<string, array<string, mixed>> $schedules Existing schedules.
     * @return array<string, array<string, mixed>>
     */
    public function register_audit_cron_schedules(array $schedules): array {
        if (!isset($schedules['prorank_biweekly'])) {
            $schedules['prorank_biweekly'] = [
                'interval' => 14 * DAY_IN_SECONDS,
                'display' => __('Every Two Weeks', 'prorank-seo'),
            ];
        }

        if (!isset($schedules['monthly'])) {
            $schedules['monthly'] = [
                'interval' => 30 * DAY_IN_SECONDS,
                'display' => __('Once Monthly', 'prorank-seo'),
            ];
        }

        return $schedules;
    }

    /**
     * Compute next run timestamp for preferred HH:MM.
     *
     * @param string $schedule_time Preferred local time in HH:MM.
     * @return int
     */
    private function get_next_schedule_timestamp(string $schedule_time): int {
        try {
            $timezone = wp_timezone();
            $now = new \DateTimeImmutable('now', $timezone);
            [$hour, $minute] = array_map('intval', explode(':', $schedule_time));
            $next = $now->setTime($hour, $minute, 0);
            if ($next <= $now) {
                $next = $next->modify('+1 day');
            }
            return $next->getTimestamp();
        } catch (\Throwable $e) {
            return time() + HOUR_IN_SECONDS;
        }
    }

    /**
     * Resolve notification recipients for scheduled audit alerts.
     *
     * @return array<int, string>
     */
    private function get_audit_notification_recipients(): array {
        $raw = (string) get_option('prorank_audit_notification_emails', '');
        $parts = array_filter(array_map('trim', explode(',', $raw)));
        $emails = [];

        foreach ($parts as $part) {
            $email = sanitize_email($part);
            if ($email !== '') {
                $emails[] = $email;
            }
        }

        if (empty($emails)) {
            $admin_email = sanitize_email((string) get_option('admin_email'));
            if ($admin_email !== '') {
                $emails[] = $admin_email;
            }
        }

        return array_values(array_unique($emails));
    }

    /**
     * Handle scheduled audit run
     */
    public function handle_scheduled_audit(): void {
        try {
            $this->rest_start_audit();
        } catch (\Exception $e) {
            prorank_log('[ProRank SEO] Scheduled audit failed: ' . $e->getMessage());
        }
    }

    /**
     * Send alert summary when triggered by cron
     */
    public function maybe_send_alert_from_status(): void {
        $status = $this->rest_get_status();
        if (!is_array($status) || empty($status['state']) || $status['state'] !== 'completed') {
            return;
        }
        $this->maybe_send_alert($status['audit_id'] ?? null);
    }

    /**
     * Create database tables
     *
     * @return void
     */
    private function create_tables(): void {
        global $wpdb;
        
        $charset_collate = $wpdb->get_charset_collate();
        
        // Drop malformed tables if they exist
        $wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}IF");
        
        // Audits table
        $table_audits = $wpdb->prefix . 'prorank_audits';
        $sql_audits = "CREATE TABLE IF NOT EXISTS $table_audits (
            id varchar(36) NOT NULL,
            status varchar(20) NOT NULL DEFAULT 'pending',
            start_time datetime DEFAULT NULL,
            end_time datetime DEFAULT NULL,
            score int(3) DEFAULT 0,
            stats longtext,
            options longtext,
            PRIMARY KEY (id),
            KEY status (status),
            KEY start_time (start_time)
        ) $charset_collate";
        
        // URLs table
        $table_urls = $wpdb->prefix . 'prorank_audit_urls';
        $sql_urls = "CREATE TABLE IF NOT EXISTS $table_urls (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            audit_id varchar(36) NOT NULL,
            url varchar(2000) NOT NULL,
            status varchar(20) NOT NULL DEFAULT 'pending',
            depth int(11) DEFAULT 0,
            PRIMARY KEY (id),
            KEY audit_id (audit_id),
            KEY status (status)
        ) $charset_collate";
        
        // Issues table
        $table_issues = $wpdb->prefix . 'prorank_audit_issues';
        $sql_issues = "CREATE TABLE IF NOT EXISTS $table_issues (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            audit_id varchar(36) NOT NULL,
            url varchar(2000) NOT NULL,
            type varchar(50) NOT NULL,
            message text NOT NULL,
            severity varchar(20) NOT NULL DEFAULT 'medium',
            data longtext,
            PRIMARY KEY (id),
            KEY audit_id (audit_id),
            KEY type (type),
            KEY severity (severity)
        ) $charset_collate";
        
        // Use direct queries instead of dbDelta to avoid parsing issues
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $wpdb->query($sql_audits);
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $wpdb->query($sql_urls);
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $wpdb->query($sql_issues);

        // Add options column to existing tables (for upgrades)
        $column_exists = $wpdb->get_var("SHOW COLUMNS FROM {$table_audits} LIKE 'options'");
        if (!$column_exists) {
            $wpdb->query("ALTER TABLE {$table_audits} ADD COLUMN options longtext AFTER stats");
        }

        // Add depth column to audit_urls table (for upgrades)
        $depth_exists = $wpdb->get_var("SHOW COLUMNS FROM {$table_urls} LIKE 'depth'");
        if (!$depth_exists) {
            $wpdb->query("ALTER TABLE {$table_urls} ADD COLUMN depth int(11) DEFAULT 0 AFTER status");
        }
    }

    /**
     * Check if tables exist
     *
     * @return bool
     */
    private function check_tables_exist(): bool {
        global $wpdb;
        
        $tables = [
            $wpdb->prefix . 'prorank_audits',
            $wpdb->prefix . 'prorank_audit_urls',
            $wpdb->prefix . 'prorank_audit_issues',
        ];
        
        foreach ($tables as $table) {
            if ($wpdb->get_var("SHOW TABLES LIKE '$table'") !== $table) {
                return false;
            }
        }
        
        return true;
    }
}
